Ch. 02 Spring - 서블릿과 JSP

2023. 1. 7. 15:37

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 안으로 들어감

 

  1. 첫번째 호출에서 변환- 컴파일-인스턴스 생성 과정에 의한 시간 지연이 있음
  2. 두번째 호출에서는 서블릿인스턴스가 바로 호출해줌
  3. 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>

localhost:8080/ch2/jstl.jsp?msg=donwoo 화면

 

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

BELATED ARTICLES

more