Ch. 02 Spring - 서블릿과 JSP
1. 서블릿과 JSP
JSP는 서블릿과 거의 비슷하고 Spring은 서블릿을 발전시킨것이다
(이전 시간에 했던 DispatcherServlet이 서블릿임) - 가벼운 마음으로 보자
@WebServlet = @Controller + @RequestMapping이고 서블릿은 HttpServlet을 반드시 상속받아야 한다.
서블릿에서는 service 메서드 고정
<서블릿 생명주기>
init() : 서블릿 초기화 - 서블릿의 생성 또는 리로딩 때, 단 한번만 수행됨
service() : 호출 될 때마다 반복적으로 수행됨
destroy() : 뒷정리 작업 - 서블릿이 제거 (unload)될 때, 단 한번만 수행됨
Servlet container가 다 자동으로 호출해서 우리는 호출할 필요가 없음.
(우리는 내용만 채워주면 됨)
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// 서블릿이 초기화할 때 자동 호출되는 메서드
//1. 서블릿의 초기화 작업 담당
System.out.println("[HelloServlet] init() is called.");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1. 입력
//2. 처리
//3. 출력
System.out.println("[HelloServlet] service() is called.");
}
@Override
public void destroy() {
// 3. 뒷정리 - 서블릿의 메모리에서 제거될때 서블릿 컨테이너에 의해 자동 호출
System.out.println("[HelloServlet] destroy() is called.");
}
}
서블릿 : 기본적으로 Singleton (싱글톤) - 1개의 인스턴스만 만들어지고 재활용된다.
JSP (Java Server Pages)
서블릿이랑 거의 비슷하고 JSP로 작성을 하면 서블릿으로 변환 됨 (ex. twoDice.jsp => twoDice_jsp.java)
jsp 위치는 src/main/webapp/ 에 두어야 함 (jsp는 따로 mapping할 필요 x)
<% ~~ %>는 메서드 영역으로 service() 내부로 들어감 (값출력시에는 <%=값 %>)
<%! ~~ %>는 클래스 영역으로 service안에 안들어가고 class 안으로 들어감
- 첫번째 호출에서 변환- 컴파일-인스턴스 생성 과정에 의한 시간 지연이 있음
- 두번째 호출에서는 서블릿인스턴스가 바로 호출해줌
- jsp 변경되면 다시 변환- 컴파일-인스턴스 생성 과정거친다
Spring과 서블릿 둘다 Singleton
서블릿 : lazy-init (요청이 오면 객체를 만듬)
Spring : early-init (요청이 오지 않아도 미리 객체 만들어 둠)
JSP의 기본 객체 : 생성없이 사용할 수 있는 객체 (참고만 하자)
<유효 범위(scope)와 속성(attribute)>
HTTP는 상태정보를 저장하지 않아서 저장소가 필요함
접근 범위, 생존기간에 따라 4개의 저장소가 있음 - Map 타입 (pageContext, application, session, request)
1. pageContext
- 지역 변수, 기본 객체(request, response)등을 저장
- 범위는 해당 페이지 안에서만 접근(읽기, 쓰기)이 가능
- ${lv}는 접근할때 반드시 저장소에 접근해야 함 (저장소 필요 이유)
- 요청할때마다 초기화 -> 앞에 사람 값 안남아 있음
2. application
WebApplication 전체에서 접근 가능한 저장소로 오직 1개만 존재함
전체가 공유하므로 개벌젹인 아이디를 저장하는건 문제가 있음
3. session
- 클라이언트마다 1개 씩 있는 개별 저장소 (로그인 관련 정보 - id, 장바구니등)
- 문제점 : 사용자마다 1개라서 최소한의 데이터를 저장해야함 (서버 부담이 제일 큼)
- 편리하다고 막 사용하면 안됨 (사용최소화 or 사용후 바로 삭제)
- 이 client가 접근한 페이지들은 모두 session에 접근이 가능
4. request
- 요청할때마다 request 객체가 1개씩 생김
- request를 받았을 때 jsp가 다른 jsp로 넘겨주는 것을 forward라고 함
- 제일 부담이 적으므로 다른 페이지에 데이터를 보낼때 request로 최대한 보냄
- 요청이 처리되는 동안만 존재하니깐 부담이 적음
속성 관련 메서드 | 설명 |
void setAttribute(String name, Object value) | 지정된 값(value)을 지정된 속성 이름(name)으로 저장 |
Object getAttribute(String name) | 지정된 이름(name)으로 저장된 속성의 값을 반환 |
void removeAttribute(String name) | 지정된 이름(name)의 속성을 삭제 |
Enumertation getAttributeNames() | 기본 객체에 저장된 모든 속성의 이름을 반환 |
URL 패턴 : @WebServlet으로 서블릿을 URL에 맵핑할 때 사용
(1번 해당안되면 2번 , 2번 해당안되면 3번 ... 이런식)
Servlet Context가 children과 servletMapping을 들고 있음
요청이 들어오면 servletMapping의 key중 똑같은게 있는지 확인하고
해당 URL 패턴의 value를 children으로 들어가서 찾는다.
종류 (우선순위순) | URL pattern | 매칭 URL |
1. exact mapping | /login/hello.do | http://localhost/ch2/login/hello.do |
2. path mapping | /login/* | http://localhost/ch2/login/ http://localhost/ch2/login/hello http://localhost/ch2/login/hello.do http://localhost/ch2/login/test |
3. extension mapping | *.do | http://localhost/ch2/hi.do http://localhost/ch2/login/hello.do |
4. default mapping | / | http://localhost/ch2/ http://localhost/ch2/hello.do http://localhost/ch2/login/hello.do http://localhost/ch2/login/hello http://localhost/ch2/login/hello.do |
Spring에서는 모든 요청을 DispatcherServlet이 받아서 처리한다.
EL(Expression Language) - 필요할 때 더 검색해보기
- 형태가 간단하고 편리하게 바뀜
- EL에서는 지역변수를 바로 쓰지못하므로 request 객체에 저장을 해서 쓴다
- 원래는 requestScope.name로 써야하는데 바로 name으로 사용가능
- (생략시 page -> request -> session -> application 순서대로 확인)
JSTL (JSP Standard Tag Library)
<c:set>, <c:if>등을 tag로 사용하기 위해 만들어짐
tag로 사용안하면 코드에 <%~~%> 를 여러번 써야해서 복잡해짐
<jstl.jsp>
<c:set var="to" value="10"/>
<c:set var="arr" value="10,20,30,40,50,60,70"/>
<c:forEach var="i" begin="1" end="${to}">
${i}
</c:forEach>
<br>
<c:if test="${not empty arr}">
<c:forEach var="elem" items="${arr}" varStatus="status">
${status.count}. arr[${status.index}]=${elem}<BR>
</c:forEach>
</c:if>
<c:if test="${param.msg != null}">
msg=${param.msg}
msg=<c:out value="${param.msg}"/>
</c:if>
Filter
- 공통적인 요청 전처리와 응답 후처리에 사용. (로깅, 인코딩등)
- 이렇게 하면 코드 중복도 제거되고 간편하다.
- Filter 여러개 적용이 가능하다 (2번에 필터 호출이 옴)
- 예를 들어 서블릿의 수행시간을 측정하는 filter를 구현하면 모든 서블릿에 구현할 필요 x
<PerformanceFilter.java>
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 1. 전처리 작업
long startTime = System.currentTimeMillis();
// 2. 서블릿 또는 다음 필터를 호출
chain.doFilter(request, response);
// 3. 후처리 작업
System.out.print("["+((HttpServletRequest)request).getRequestURI()+"]");
System.out.println(" 소요시간="+(System.currentTimeMillis()-startTime)+"ms");
}
doFilter라는 method를 override해서 2번은 나두고 전처리 작업과 후처리 작업만 바꿔주면 됨.
2. @RequestParam과 @ModelAttribute
@RequestParam : 요청의 파라미터를 연결할 매개변수에 붙이는 애너테이션
@RequestMapping("/requestParam2")
// public String main2(@RequestParam(name="year", required=false) String year) { 아래와 동일
// required는 필수 여부
public String main2(String year) {
// http://localhost/ch2/requestParam2 ---->> year=null
// http://localhost/ch2/requestParam2?year ---->> year=""
System.out.printf("[%s]year=[%s]%n", new Date(), year);
return "yoil";
}
@RequestMapping("/requestParam3")
// public String main3(@RequestParam(name="year", required=true) String year) { 아래와 동일
public String main3(@RequestParam String year) {
// http://localhost/ch2/requestParam3 ---->> year=null 400 Bad Request. required=true라서
// http://localhost/ch2/requestParam3?year ---->> year=""
System.out.printf("[%s]year=[%s]%n", new Date(), year);
return "yoil";
}
필수입력이 아닐때는 기본값을 줘야 함
@RequestMapping("/requestParam8")
public String main8(@RequestParam(required=false) int year) {
// http://localhost/ch2/requestParam8 ---->> 500 java.lang.IllegalStateException: Optional int parameter 'year' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.
// http://localhost/ch2/requestParam8?year ---->> 400 Bad Request, nested exception is java.lang.NumberFormatException: For input string: ""
System.out.printf("[%s]year=[%s]%n", new Date(), year);
return "yoil";
}
필수 입력때는 예외 처리를 해줘야 함
@RequestMapping("/requestParam9")
public String main9(@RequestParam(required=true) int year) {
// http://localhost/ch2/requestParam9 ---->> 400 Bad Request, Required int parameter 'year' is not present
// http://localhost/ch2/requestParam9?year ---->> 400 Bad Request, nested exception is java.lang.NumberFormatException: For input string: ""
System.out.printf("[%s]year=[%s]%n", new Date(), year);
return "yoil";
}
[참고] - 예외처리
@ExceptionHandler(Exception.class)
public String catcher(Exception ex) {
return "yoilError";
}
Parameter 많을 때는 그냥 class하나 만들어서 하는게 편함
@RequestMapping("/getYoilMVC4")
public String main(MyDate date, Model model)throws IOException{
<MyDate.java>
public class MyDate {
private int year;
private int month;
private int day;
// getter, setter 구현해서 받아오고 입력하기
<yoil.jsp>
<p> ${myDate.year }년 ${myDate.month }월 ${myDate.day }일은 ${yoil }요일 입니다.</p>
@ModelAttribute
- 적용 대상을 Model의 속성으로 자동 추가해주는 애너테이션 (모델에 자동 저장)
- 반환 타입 또는 컨트롤러 메서드의 매개변수에 적용 가능
- 사용하면 호출도 필요없고 결과 저장도 필요 없음
- 참조형 매개변수 앞에 ModelAttribute를 붙힐 수 있고 생략 가능
@RequestMapping("/getYoilMVC5")
//public String main(@ModelAttribute("myDate") MyDate date, Model model)throws IOException{ // 아래와 동일
public String main(@ModelAttribute() MyDate date, Model model)throws IOException{
// 1. 유효성 검사
if(!isValid(date))
return "yoilError";
// 2. 요일 계산
//char yoil = getYoil(date);
// 3. 계산한 결과 모델에 저장
//model.addAttribute("myDate",date);
//model.addAttribute("yoil",yoil);
return "yoil"; // /WEB-INF/views/yoil.jsp
}
즉, Controller의 매개변수의 type이 기본형이나, String일때는 @RequestParam이 생략되어 있고
참조형일때는 @ModelAttribute가 생략되어 있다고 생각해라
WebDataBinder
- getYoilMVC5?year=2021&month=10&day=1로 요청을 받으면 "2021","10","1"이 String의 형태로 테이블에 존재하고 MyDate date의 year, month, day는 int 형이므로 타입 변환을 해야하는데 그 때 WebDataBinder사용
- WebDataBinder는 타입 변환과 데이터 검증 역할을 함
- 결과나 에러가 있으면 BindingResult에 저장하고 결과를 컨트롤러에게 넘겨 줄 수 있음
- 매개변수 위치가 Binding할 매개변수 바로 뒤에 와야함
@RequestMapping("/getYoilMVC6")
public String main(MyDate date, BindingResult result )throws IOException{
// 1. 유효성 검사
if(!isValid(date))
return "yoilError";
// 2. 요일 계산
//char yoil = getYoil(date);
// 3. 계산한 결과 모델에 저장
//model.addAttribute("myDate",date);
//model.addAttribute("yoil",yoil);
return "yoil"; // /WEB-INF/views/yoil.jsp
}
'Spring > 스프링의 정석' 카테고리의 다른 글
Ch. 02 Spring 쿠키와 세션 (0) | 2023.01.09 |
---|---|
Ch. 02 Spring - Redirect와 Forward (0) | 2023.01.08 |
Ch. 02 Spring MVC - MVC 패턴 (0) | 2023.01.04 |
Ch. 02 Spring MVC - 2 (0) | 2023.01.03 |
Ch. 02 Spring MVC - 1 (0) | 2023.01.02 |