본문 바로가기

Spring

토비의 스프링 vol.2 4장 @MVC

4장 스프링 @mvc

4.4 JSP 뷰와 form 태그

  • 스프링 MVC 뷰 기술과 작성 방법은 매우 다양하다. 하지만 컨트롤러를 통해 준비된 모델 오브젝트를 활용해서 클라이언트가 보게 될 콘텐트를 작성한다는 면에서는 모두 동일하다.
  • 이 절에서는 주로 폼을 이용한 뷰를 작성할 때 필요한 스프링 지원 기능에 대해 설명한다.
  • 폼을 다룰 수 있는 뷰 기술 중 JSP/JSTL 만 설명한다.

4.4.1 EL과 SPRING 태그 라이브러리를 이용한 단순한 모델 출력

JSP EL(Expression Language)

EL(Expression Language)은 자바 빈의 프로퍼티 값을 JSP의 표현식 <%= %>이나 액션 태그 jsp:useBean를 사용하는것 보다 쉽고 간결하게 꺼낼수 있게 하는 기술이다.
또한 static 메소드를 호출할 수도 있는데 JSP에서는 주로 서블릿 보관소(JspContext, ServletRequest, HttpSession, ServletContext)에서 값을 꺼낼 때 사용한다.

// Controller 메소드에서 다음과 같이 모델 오브젝트를 추가
model.addAttribute("name", "Spring");

// JSP 뷰에서 name을 출력하고 싶다면
<div>이름 : ${name} </div>

${user.age}

스프링 SpEL

  • JSP EL보다 유연하고 강력한 표현식을 지원한다.
  • JSP 뷰에서 스프링의 SpEL을 사용하려면 다음과 같이 spring 태그 라이브러리를 추가해야 한다.
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
  • SpEL은 오브젝트의 메소드 호출이 가능하다
<spring:eval expression='user.name"/>
  • SpEL은 다양한 논리, 산술 연산을 지원한다. 또 클래스를 지정해서 스태틱 메소드를 호출할 수 있다.
  • 심지어 new 키워드를 이용해 오브젝트를 만들고, 메소드를 실행할 수도 있다.
  • SpEL이 JSP EL에 비해 훨씬 기능이 뛰어난 표현식 언어임에는 분명하지만 그렇다고 뷰에서 복잡한 표현식을 사용하면서 과용하는 것은 바람직하지 않다.

4.4.2 spring 태그 라이브러리를 이용한 폼 작성

단일 폼 모델 - HTML의

태그
  • 폼 뷰를 사용하는 두 가지 시나리오
    • 등록 : 처음에 빈 폼이 뜨고 사용자 입력을 받아서 이를 저장하는 폼
    • 수정 : 처음부터 폼에 내용이 채워서 출력되고 이를 수정해서 저장하는 폼
    • 둘다 처음에 폼을 띄울 때부터 모델의 정보를 폼에 출력하는 방식을 사용해야함
      • 사용자의 입력 값에 오류가 있을 때 다시 폼을 띄우면서 기존 입력 값을 보여줘야 하기 때문

스프링 MVC의 폼 처리 과정

  1. 먼저 폼에 출력할 모델 오브젝트 결정
  2. 여기서는 USER 오브젝트를 모델로 사용
  3. 폼을 그릴 폼 뷰를 JSP로 만든다.
  4. 이 폼을 처음 출력할 때는 GET 메소드의 요청을 사용한다.

  1. 등록 화면이라면 빈 USER 오브젝트가 모델로 올 것이고, 수정 화면이라면 DB에서 읽어온 USER 오브젝트가 폼으로 전달될것이다.
  2. 사용자는 폼을 통해 원하는 값을 입력하거나 수정한다.
  3. 저장 버튼을 누르면 이번에는 POST 메소드로 서버에 요청이 날아간다.

  1. 이때 바인딩과 함께 검증 작업도 일어나는데, 오류가 하나라도 발견되면 컨트롤러는 같은 폼 뷰를 다시 띄워서 기존 모델의 값과 함께 에러 메시지를 보여줘야 한다. (처음과 동일한 JSP 뷰)

JSP EL 사용시 한계점

  • 바인딩 오류가 났을 때 에러 메시지를 출력할 수 없다.
  • 잘못 입력한 값을 출력할 수 없다.
  • spring:bind라는 태그를 사용하자.

spring:bind와 BindingStatus

<spring:bind path="user.name">
    <label for="name">Name : </label>
    <input type="text" id="${status.expression}" name="${status.expression}" value="${status.value}"
</spring:bind>
  • 바인딩 오류 시 기존에 잘못 입력한 값을 보여주는 기능 해결
  • status 변수의 errorMessages를 사용하여 에러 메시지 출력 못하는 문제 해결

4.4.3 form 태그 라이브러리

  • 스프링의 form 태그 라이브러리를 이용하면 spring:bind보다 훨씬 간결한 코드로 동일한 기능을 하는 코드를 만들 수 있다.
  • form 태그를 사용하려면 JSP 파일 앞부분에 다음과 같이 태그 라이브러리를 선언해줘야 한다.
<$@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %

form:form

  • form 을 만들어 준다. HTML의
  • 자주 사용하는 attributes : commandName, modelAttribute, method, action

form:input

  • HTML의 태그를 생성한다.
  • input 태그에 적용 가능한 size, maxlength, readonly 같은 표즌 HTML 애트리뷰트와 onclick, onkeydown 같은 이벤트 애트리뷰트 등을 지원한다.

form:label

  • 폼의 레이블 출력에 사용되는

form:errors

  • 바인딩 에러 메시지를 출력할 때 사용

form:hidden

  • 태그를 작성한다.
  • 폼을 띄울 때 참고한 모델 오브젝트를 저장해뒀다가 폼을 바인딩할 때 다시 사용하기 때문에, 폼에 출력하지 않는 프로퍼티 값은 그대로 유지할 수 있다.

form:password, from:textarea, form:checkbox, form:radiobuttons, form:select, form:option, form:options

4.5 메시지 컨버터와 AJAX

메시지 컨버터는 XML이나 JSON을 이용한 AJAX 기능이나 웹 서비스를 개발할 때 사용할 수 있다. HTTP 요청 프로퍼티를 모델 오브젝트의 프로퍼티에 개별적으로 바인딩하고 모델 오브젝트를 다시 뷰를 이용해 클라이언트로 보낼 콘텐트로 만드는 대신 HTTP 요청 메시지 본문과 HTTP 응답 메시지 본문을 통째로 메시지로 다루는 방식이다. 메시지 컨버터는 파라미터의 @RequestBody와 메소드에 부여한 @ResponseBody를 이용해서 쓸 수 있다.

4.5.1 메시지 컨버터의 종류

사용할 메시지 컨버터는 AnnotationMethodHandlerAdapter를 통해 등록한다.

디폴트 등록 HttpMessageConverter

  • ByteArrayHttpMessageConverter : 요청, 응답 메시지에 바이너리 정보(바이트단위)가 필요한 경우 @RequestBody, @ResponseBody 변환해준다.
  • StringHttpMessageConverter : HTTP 요청 본문을 그대로 스트링으로 가져올 수 있다. 가공하지 않은 본문을 직접 받아서 사용하고 싶은 경우라면 유용함.
  • FormHttpMessageConverter : 미디어 타입이 application/x-www-form-urlencoded로 정의된 폼 데이터를 주고받을 때 사용할 수 있다. HTTP 요청의 폼 정보는 @ModelAttribute를 이용해 바인딩하는 것이 훨씬 편리하고 유용하므로 잘 사용하지 않음.
  • SourceHttpMessageConverter : 미디어 타입이 application/xml

그 외가 더 자주 사용됨

  • MappingJacksonHttpMessageConverter : Jackson ObjectMapper를 이용해서 자바오브젝트와 JSON 문서를 자동변환해주는 메시지 컨버터. 지원 미디어 타입은 application/json

JSON을 이용한 AJAX 컨트롤러: GET + JSON

메시지 컨버터를 이용해 간단한 JSON 애플리케이션을 만들어보자.

  • 예제 : 사용자 등록 화면에서 폼을 서브밋하기 전에 아이디 중복검사하는 기능

  • 여기서 아이디 중복검사 버튼을 누르면 AJAX 방식으로 서버에 요청을 보내서 입력된 아이디가 이미 존재하는지 확인한다. (/user/checkloginid/ceoahn)
  • 이 URL에서 로그인 아이디를 파라미터로 받을 수 있도록 다음과 같이 컨트롤러 메소드를 정의한다.
@RequestMapping(value="checkloginid/{loginId}", method=RequestMethod.GET)
public Result checklogin(@PathVariable String loginId){
...
}

//아이디 확인 결과용 클래스
public class Result{
    boolean duplicated;
    String availableId; //사용가능한 아이디를 생성해서 전달
...
}
  • Result 오브젝트에 결과를 담은 뒤 이를 다시 JSON으로 변환해서 클라이언트로 보내야 한다. 다음과 같이 JSON 변환을 지원하는 MappingJacksonHttpMessageConverter를 AnnotationMethodHandlerAdapter 빈의 messageConverters 프로퍼티에 추가해준다.
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
        </list>
    </property>
</bean>
  • @ResponseBody 어노테이션 추가 및 메소드 구현
@RequestMapping(value="checkloginid/{loginId}", method=RequestMethod.GET)
@ResponseBody
public Result checklogin(@PathVariable String loginId){
    Result result = new Result();
    if(userservice.isRegisteredLoginId(loginId)){
        result.setDuplicated(true);
        result.setAvailableId(loginId + (int)(Math.random()*1000));
    }
    else {
        result.setDuplicated(false);
    }
    return result;
}
  • 이제 자바스크립트를 사용해 위의 URL을 호출해주고 그 결과를 받아서 사용자에게 알려주는 코드를 만들면 된다.
  • jQuery 활용

  • 결과확인

JSON을 이용한 AJAX 컨트롤러: POST(JSON) + JSON

  • JQuery의 AJAX 기능을 이용해서( $.postJSON ) POST 메소드로 요청을 보낸다.
  • 위의 예제와 유사하여 생략

4.6 MVC 네임스페이스

스프링 3.0에서는 대거 도입된 @MVC 관련 기능을 제대로 활용하기 위해 mvc 스키마의 전용 태그를 제공한다. 이를 활용하면 애노테이션 방식의 포맷터를 사용하는 바인딩 기능이나, JSR-303의 빈 검증 기능, 메시지 컨버터 등 여러 가지 프로퍼티 설정들을 손쉽게 할 수 있다.

mvc:annotation-driven

이 태그는 애노테이션 방식의 컨트롤러를 사용할 때 필요한 DispatcherServlet 전략 빈을 자동으로 등록해준다. 또, 최신 @MVC 지원 기능을 제공하는 빈도 함께 등록하고 전략 빈의 프로퍼티로 설정해준다.

  • DefaultAnnotationHandlerMapping : 가장 우선적으로 @RequestMapping을 이용한 핸들러 매핑 전략을 등록한다. 다른 디폴트 핸들러 매핑 전략은 자동등록되지 않는다.
  • AnnotationMethodHandlerAdapter : DispatcherServlet이 자동으로 등록해주는 디폴트 핸들러 어댑터다. (HandlerAdapter : 이 인터페이스는 HandlerMapping에서 결정된 핸들러 정보로 해당 메서드를 직접 호출해 주는 스펙이다.)
  • ConfigurableWebBindingInitializer
    • 모든 컨트롤러 메소드에 자동으로 적용되는 WebDataBinder 초기화용 빈을 등록하고 AnnotationMethodHandlerAdapter의 프로퍼티로 연결해준다.
    • 기본적으로 컨버전 서비스는 @NumberFormat과 같은 애노테이션 방식의 포맷터를 지원하는 FormattingConversionServiceFactoryBean이 등록된다.
    • 글로벌 검증기는 LocalValidatorFactoryBean으로 설정된다. 따라서 JSR-303의 검증용 애노테이션 기능이 자동으로 제공된다.
  • 메시지컨버터 : 네 개의 디폴트 메시지 컨버터와 함께 Jaxb2RootElementHttpMessageConverter와 MappingJacksonHttpMessageConverter가 추가로 등록된다.
  • validator : 모든 컨테이너에 일괄 적용하는 검증기는 디폴트로 추가되는 JSR-303 방식의 LocalValidatorFactoryBean이면 충분하다. 다른 검증기가 필요하면 등록해줄 수 있다.
  • conversion-service : ConfigurableWebBindingInitializer의 conversionService 프로퍼티에 설정될 빈을 직접 지정할 수 있다. 스프링이 제공하는 컨버전 서비스만 사용한다면 디폴트로 등록되는 FormattingConversionServiceFactoryBean으로 충분하겠지만, 직접 개발한 컨버터나 포맷터를 적용하려면 직접 등록하고 재구성해줘야 한다.
<mvc:annotation-drivcen conversion-service="myConversionService"/>
<bean id="myConversionService"> class="FormattingConversionServiceFactoryBean">
    <property name="converters">
...
    </property>
</bean>

mvc:interceptors

HandlerInterceptor 적용방법

  1. 핸들러 매핑 빈의 interceptors 프로퍼티를 이용해 등록하는 방법이다.
    • 단점 1. 인터셉터 등록을 위해 핸들러 매핑 빈을 명시적으로 빈으로 선언해줘야 한다는 점
    • 단점 2. 핸들러 매핑이 여러 개라면 핸들러 매핑마다 인터셉터를 반복해서 설정해줘야 한다는 점
  2. mvc:interceptors를 이용
    • 일괄 적용되는 인터셉터를 한 번에 설정
    • 인터셉터를 등록하려고 디폴트 핸들러 매핑 빈을 설정파일에 등록해주지 않아도 된다.
    • url 패턴을 지정할 수 있다.
    • <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/admin/*"/> <bean class="...AdminInterceptor"/> </mvc:interceptor> </mvc:interceptors>
    • url과 상관없이 모든 요청에 적용할 인터셉터라면 mvc:interceptors 안에 빈을 직접 등록해주면 된다.
    • <mvc:interceptors> <bean class="...MyInterceptor"/> </mvc:interceptors>

mvc:view-controller

컨트롤러가 하는 일이라곤 뷰를 지정하는 것뿐인 경우가 있다. 모델도 없고 컨트롤러 로직도 없이 요청에 대해 특정 뷰를 지정해주는 게 전부인 컨트롤러가 있다. 굳이 이런 기능을 위해 컨트롤러를 만드는 건 번거로운 일이다.

<mvc:view-controller path="/" view-name="/index" />

4.7@MVC 확장 포인트

4.7.1 AnnotationMethodHandlerAdapter

SessionAttributeStore

  • @SessionAttributeStore에 의해 지정된 모델은 자동으로 HTTP 세션에 저장됐다가 다음 요청에서 사용할 수 있다. 정확히 말하자면 SessionAttributeStore 인터페이스의 구현 클래스에 의해 저장됐다가 다시 참조할 수 있는 것이다.

WebArgumentResolver

  • 컨트롤러 메소드의 파라미터로 사용할 수 있는 타입과 애노테이션의 종류는 20여 가지나 된다.
  • 웬만한 HTTP 요청정보는 메소드 파라미터 선언을 통해 원하는 형태로 전달 받을 수 있다.
  • 하지만 필요하다면 이를 더 확장할 수 있다 → WebArgumentResolver

예제 : 쿠키 값을 사용하는 컨트롤러

public void add(@CookieValue String encodedUserInfo){
    User currentUser = userService.decodeUserInfo(encodedUserInfo);
...
}
  • 매번 코드를 통해 사용자 정보를 가져오는 작업이 번거롭다.
  • WebArgumentResolver를 이용해 쿠키 값으로부터 사용자 정보를 가져와 메소드 파라미터로 전달받도록 만들 수 있다. 다음의 인터페이스를 구현한다.
public interface WebArgumentResolver{
    Object UNRESOLVED = new Object();
    Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception;
  • 이를 이용하면 간단히 다음과 같은 메소드 파라미터를 사용할 수 있다.
public void add(@Current User user){ ...}

4.8 URL과 리소스 관리

4.8.1 mvc:default-servlet-handler/를 이용한 URL 관리

디폴트 서블릿과 URL 매핑 문제

  • 클라이언트가 서버에 요청을 보낼 때 사용하는 URL에는 동적인 기능을 가진 페이지와 고정된 리소스 파일을 구분하는 표준이 엄격하게 존재하지 않는다.
  • 이전에는 확장자를 기준으로 접근하는 대상을 구분했다.(view.do, add.do, hello.html, hello.jsp 등)
  • 최근에는 REST 스타일의 URL을 작성하는 경우가 많아서 확장자로 구분하기 어렵다.
  • 이러한 변화는 servlet의 매핑에도 영향을 주었다.
  • 확장자 有
  • <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class/> </serlvet> <servlet-mapping> <servlet-name>spring</servlet-name> <servlet-name>*.do</servlet-name> </servlet-mapping>
  • 확장자 無
  • <servlet-mapping> <servlet-name>spring</servlet-name> <servlet-name>/app/*</servlet-name> </servlet-mapping>
  • 이렇게 /app 경로 아래의 모든 내용을 스프링 DispatcherServlet이 처리하도록 매핑하면 JSP나 그 밖의 정적인 리소스와 깔끔하게 구분할 수 있다.
  • 그런데 /app처럼 DispatcherServlet이 처리하는 동적인 페이지를 구분하고자 앞에 붙인 경로를 없애고싶다.
  • 이렇게 바꾸면 서블릿 컨테이너는 /로 시작하는 모든 URL을 모두 DispatcherServlet이 처리하는 것으로 기대하고 DispatcherServlet에 전달한다. 따라서 /를 매핑한 DispatcherServlet에는 /index.html이나 /js/jquery.js, /css/theme/default.css 같은 URL 요청도 모두 전달된다. DispatcherServlet의 매핑 전략은 이런 URL을 처리할 핸들러를 찾을 수 없으니 404 에러가 나고 말 것이다.

서블릿 컨테이너의 디폴트 web.xml (톰캣 7에는 다음의 두 개의 서블릿이 디폴트로 정의되어있다.)

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class/>
    ...
    <load-on-startup>1</load-on-startup>
</serlvet>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class/>
    ...
    <load-on-startup>3</load-on-startup>
</serlvet>
<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
    <url-pattern>*.jspx</url-pattern>
</servlet-mapping>
  • jsp 서블릿은 jsp나 jspx 확장자를 가진 모든 요청을 JspServlet으로 보내주고, 다른 서블릿 매핑에서 처리되지 않은 모든 나머지 URL은 DefaultServlet이 처리한다.
  • 그래서 웹 애플리케이션에 별다른 설정을 하지 않더라도 이미지나 js 파일 같은 정적인 내용도 서블릿 컨테이너가 처리할 수 있는 것이다.
  • 그런데 웹 애플리케이션의 서블릿을 /에 매핑해버리면 디폴트 web.xml에 정의된 DefaultServlet보다 우선한다. DispatcherServlet이 /로 시작하는 모든 URL을 담당하기 때문에 문제가 되는 것이다.
  • 위의 문제에 대한 해결책
    • UrlRewriteFilter : 패턴에 따라 URL을 변경할 수 있다. /user/1요청이 들어오면 /app/user/1로 바꿔서 서블릿으로 전달한다.
    • mvc:default-servlet-handler/

mvc:default-servlet-handler/

  • xml에 위의 설정만 넣어주면 해결된다.
  • /로 시작하는 모든 URL이 디폴트 서블릿 대신 DispatcherServlet으로 매핑되는 것은 변함없다.
  • 대신 DispatcherServlet에 정적 리소스 파일에 대한 요청을 디폴트 서블릿으로 포워딩하는 기능이 추가될 뿐이다.
  • DispatcherServlet이 요청을 받으면 먼저 @RequestMapping의 요청 조건에 맞는지 확인한다.
  • 만약 요청을 처리할 핸들러를 찾지 못하면 해당 요청은 정적 리소스라고 판단하고 디폴트 서블릿으로 넘기는 것이다.

4.8.2 url:resource/를 이용한 리소스 관리

  • 정적 리소스를 jar로 패키징하여 WEB-INF/lib 에 넣는다.
  • 특정 URL 요청이 들어왔을 때 클래스패스로만 접근 가능한 jar 파일의 내용이 사용하도록 XML 설정을 해준다.
  • <mvc:resources mapping="/ui/**" location="classpath:/META-INF/webresources/"/>

4.9 스프링 3.1의 @MVC

스프링 3.1에 새롭게 도입된 DispatcherServlet의 전략에 대해 설명할 것이다.

4.9.1 새로운 RequestMapping 전략

@RequestMapping 메소드와 핸들러 매핑 전략의 불일치

  • 기존의 @RequestMapping은 확장성이 떨어졌었다.(스프링 3.0 이전)
  • @RequestMapping을 담당하는 DefaultAnnotationHandlerMapping 같은 전략 클래스가 DispatcherServlet 전략의 설계 의도와 맞지 않는 부분이 있었다.
  • HandlerMapping 전략의 목적
    • HTTP 요청을 처리할 핸들러 오브젝트를 찾는 것
  • 핸들러 매핑 전략은 URL 같은 요청정보를 기준으로 요청을 처리할 핸들러를 매핑해준다.
  • 이 핸들러는 결국 스프링 컨테이너가 관리하는 빈 오브젝트다.
  • 핸들러 매핑 전략에서 요청을 처리할 핸들러 오브젝트가 결정된 후에 이를 실행하는 책임은 HandlerAdapter를 구현한 핸들러 어댑터 전략이 맡았다.

→ 매핑 전략과 실행 전략을 깔끔하게 분리해서 다양한 방식으로 매핑할 수 있고, 다양한 핸들러 종류를 추가할 수 있도록 유연하게 설계된 것이다. 그리고 그 중심에는 매핑을 통해 선정된 핸들러 오브젝트, 즉 컨트롤러 오브젝트가 있다.

  • 그런데 @RequestMapping을 지원하면서부터 이런 전략 구조가 매끄럽지 않아졌다.
  • @RequestMapping은 웹 요청을 컨트롤러 오브젝트가 아니라 오브젝트 내의 특정 메소드에 매핑하도록 설계된 것이다.
  • 이전에는 핸들러 매핑이 컨트롤러 빈의 오브젝트만 찾아내면 됐는데 @RequestMapping을 적용한 경우에는 오브젝트가 아니라 메소드로 매핑을 할 필요가 생긴것이다. 자바의 메소드는 오브젝트로 취급되지 않으니 빈이 될 수가 없다.
  • 그렇기에 스프링 3.0에서는 DefaultAnnotationHandlerMapping 전략에선 매핑결과가 오브젝트가 될 수밖에 없고 AnnotationMethodHandlerAdapter가 실행할 메소드를 찾는 매핑 작업을 추가로 해야했다. 핸들러 어댑터에게 주어진 역할이 아닌 매핑 역할을 부분적으로 갖고 있는 이상한 구조가 될 수밖에 없었다.
  • 책임과 역할이 엉성하게 얽혀있었음!!
  • 스프링 3.1에서는 @RequestMapping을 처리하는 전략 클래스를 새롭게 설계해서 이 문제를 해결했다.
public class AuditInterceptor extends HandlerInterceptorAdapater {
    @override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
        Object handler) throws Exception{

        HandlerMethod hm = (HandlerMethod)handler;
        if(hm.getMethodAnnotation(Audit.class) !=null){
            saveAuditInfo(request, response, handler);
        }

    ...
}
  • 파라미터로 전달받은 handler를 HandlerMethod 타입으로 캐스팅했다.
  • 기존 핸들러 매핑 전략을 사용했다면 이 handler 파라미터의 타입은 컨트롤러 클래스인 jobController 였을 것이다. 하지만 스프링 3.1은 HandlerMethod라는 새로운 타입의 오브젝트를 핸들러로 넘겨준다.

HandlerMethod

  • HandlerMethod는 @RequestMapping이 붙은 메소드의 정보를 추상화한 오브젝트 타입이다.
  • 컨트롤러 오브젝트 대신 추상화된 메소드 정보를 담은 오브젝트를 핸들러 매핑의 결과로 돌려주고, 핸들러 어댑터는 HandlerMethod 오브젝트의 정보를 이용해 메소드를 실행한다.
  • HandlerMethod에 담긴 정보
    • 빈 오브젝트
    • 메소드 메타정보
    • 메소드 파라미터 메타정보
    • 메소드 애노테이션 메타정보
    • 메소드 리턴 값 메타정보
  • 웹 요청을 HandlerMethod 오브젝트에 매핑하는 RequestMappingHandlerMapping은 DispatcherServlet이 시작될 때 모든 컨트롤러 빈의 메소드를 살펴서 매핑 후보가 될 메소드를 추출한 뒤 이를 handlerMethod 형태로 저장해두고, 실제 요청이 들어오면 저장해둔 목록에서 요청 조건에 맞는 HandlerMethod 오브젝트를 찾아 돌려준다.

4.9.2 @RequestMapping 핸들러 매핑: RequestMappingHandlerMapping

RequestMappingHandlerMapping의 역할은 클라이언트의 요청을 @RequestMapping이 붙은 메소드로 매핑해주는 것이다. 매핑 결과는 @RequestMapping 메소드의 정보를 추상화한 HandlerMethod 타입 오브젝트다.

요청조건

  • 핸들러 매핑이 다루는 요청은 서블릿이 전달받는 HTTP 서블릿 요청이다.
  • @RequestMapping에 포함된 요청조건

4.9.3 @RequestMapping 핸들러 어댑터

HandlerMethod 타입의 핸들러를 찾고 이를 통해 메서드를 실행시켜주는 객체

파라미터 타입

  • 스프링 3.1에서 새롭게 지원하는 파라미터 타입들이다.
  • @Validated/@Valid
  • @Valid와 @RequestBody
  • @UriComponentsBuilder

4.9.4 @EnableWebMvc와 WebMvcConfigurationSupport를 이용한 @MVC 설정

스프링 3.1의 최신 @MVC전략을 사용하려면 XML 설정에 mvc:annotation-driven을 넣거나 인프라 빈 설정용 애노테이션을 이용해 @MVC빈이 등록되게 해야한다. @MVC의 디폴트 설정을 그대로 사용한다면 mvc:annotation-driven으로 충분하겠지만 @MVC 전략과 관련된 설정을 넣으려면 mvc 네임스페이스의 전용 태그는 불편하다. 본격적으로 @MVC의 기능을 활용하려면 이제부터 설명할 자바 코드를 이용한 @MVC 빈 등록 및 설정 방식을 사용하는 것이 좋다.

@EnableWebMvc와 WebMvcConfigurer

  • @Configuration 클래스에 @EnableWebMvc를 붙여주면 mvc:annotation-config/을 XML에 넣었을 때와 동일하게 스프링 3.1의 최신 전략 빈이 등록된다.
  • 추가 설정을 위해선 WebMvcConfigurer를 구현한 클래스를 만들어 빈으로 등록해주면 된다.

@MVC 설정자 빈 등록 방법

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer{
...
}

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter{
...
}
  • WebMvcConfigurerAdapter는 WebMvcConfigurer를 구현한 어댑터 클래스다. 설정이 필요한 메소드만 오버라이딩해서 사용하면 된다.

4.10 정리

  • 스프링 웹기술의 가장 큰 장점은 유연성을 활용해서 개별 애플리케이션의 특성에 맞춰 확장하고 최적화할 수 있다는 점이다.

참고

JSP - EL 표현식 문법과 사용 방법

HandlerMapping 와 HandlerAdapter - 머루의개발블로그

'Spring' 카테고리의 다른 글

Servlet ?  (0) 2022.01.05
Fetch join, N+1 문제  (0) 2021.11.19
객체 지향 설계와 스프링  (0) 2020.10.19
SOLID - 좋은 객체지향 설계의 5가지 원칙  (0) 2020.10.19
좋은 객체 지향 프로그래밍이란?  (0) 2020.10.19