[Spring] 스프링 MVC 동작방식

첫걸음 - 10

Posted by owin2828 on 2019-12-30 16:21 · 6 mins read

1. 스프링 MVC 핵심 구성 요소


MVC2
MVC 프레임워크의 흐름은 다음과 같다.

  1. DispatcherServlet모든 연결을 담당하며, 웹 브라우저로부터 요청을 받음
    • 요청이 들어오면 DispathcerServlet는 HandlerMapping Bean 객체에게 컨트롤러 검색을 요청
    • HandlerMapping은 클라이언트의 요청 경로를 이용해 컨트롤러 Bean객체를 DispathcerServlet에게 전달
  2. DispathcerServlet은 전달받은 객체를 처리할 수 있는 HandlerAdapter Bean에게 요청 처리를 위임
  3. HandlerAdapter는 컨트롤러의 알맞은 매서드를 호출해 요청을 처리
  4. 그 후, 결과를 ModelAndView라는 객체로 DispathcerServlet에게 반환
    • DispathcerServlet는 전달받은 결과를 보여줄 뷰를 찾기 위해 ViewResolver Bean 객체를 사용
    • ViewResolver는 ModelAndView 내부에 있는 뷰 이름에 해당하는 View 객체를 찾거나 생성후 리턴
  5. DispathcerServlet는 VeiwResolver가 리턴한 View 객체에게 응답 결과 생성을 요청

1-1. 컨트롤러와 핸들러

  • 클라이언트의 요청을 실제로 처리하는 것은 컨트롤러
  • DispathcerServlet는 클라이언트의 요청을 전달받는 창구 역할
  • HandlerMapping은 특정 요청 경로를 처리해주는 핸들러를 찾아주는 역할
  • HandlerAdapter는 핸들러의 처리결과를 ModelAndView 객체로 변환하여 DispathcerServlet에게 전달

2. @Controller를 위한 HandlerMapping과 HandlerAdapter


    // ControllerConfig.java
    @Configuration
    @EnableWebMvc
    public class MvcConfig{
        ...
    }
  • @EnableWebMvc 어노테이션을 통해 HandlerMapping이나 HandleAdapter 설정을 자동으로 추가
    // HelloController.java
    @Controller
    public class HelloController {

        @GetMapping("/hello")
        public String hello(Model model,
                @RequestParam(value = "name", required = false) String name) 
            {
            model.addAttribute("greeting", "안녕하세요, " + name);
            return "hello";
        }
    }
  • @EnableWebMvc 어노테이션은 @Controller 타입의 핸들러 객체를 처리하기 위한 클래스를 포함
  • 그 중 하나가 RequestMappingHandlerMapping이며,
    이 어노테이션은 @GetMapping 어노테이션 값을 이용해서 웹 브라우저의 요청을 처리할 컨트롤러 Bean을 찾음

    GET & POST여기를 참조

  • 앞선 코드에서 @GetMapping에서 “/hello”라는 요청 경로에 대해 hello() 매서드를 호출
    이때 Model 객체를 생성해 첫 번째 파라미터로, HTTP 요청 파라미터를 두 번째 파라미터로 전달
  • RequestMappingHandlerAdapter는 컨트롤러 매서드 결과 값이 String 타입이면,
    해당값을 뷰 이름으로 갖는 ModleAndView 객체를 생성해 DispatcherServlet에 리턴

    위의 예제에서 결국 뷰의 이름은 hello가 된다.

3. JSP를 위한 ViewResolver


  • 컨트롤러 처리 결과를 JSP를 이용해서 생성하기 위해 다음 설정을 사용
      // MvcConfig.java
      @Configuration
      @EnableWebMvc
      public class MvcConfig implements WebMvcConfigurer {
          ...
          @Override
          public void configureViewResolvers(ViewResolverRegistry registry) {
              registry.jsp("/WEB-INF/view/", ".jsp"); // 접두사, 접미사 설정
          }
      }
    
  • WebMvcConfigurer 인터페이스에 정의된,
    • configureViewResolvers() 매서드의 파라미터 ViewResolverRegistry의 jsp() 매서드를 이용해 ViewResolver를 설정가능
    • 내부 클래스를 이용하여 다음과 같이 뷰 이름에 해당하는 객체를 요청
        @Bean
        public ViewResolver viewResolver(){
         InternalResourceViewResolver vr = 
             new InternalResourceViewResolver();
         vr.setPrefix("/WEB-INF/view/");
         vr.setSurfix(".jsp");
         return vr;
        }
      
  • 앞선 코드의 구성처럼, "prefix + 뷰이름 " surfix"에 해당하는 경로를 할당
  • DispatcherServlet이 View 생성을 요청하면 InternalResourceViewResolver는 이 JSP 코드를 실행하여 결과 생성

4. 디폴트 핸들러와 HandlerMapping의 우선순위


  • web.xml 파일을 참조하면, DispatcherServlet에 대한 매핑 경로가 다음과 같이 '/'로 설정
      <!-- web.xml -->
      <servlet-mapping>
          <servlet-name>dispatcher</servlet-name>
          <url-pattern>/</url-pattern>
      </servlet-mapping>
    
  • 매핑 경로가 ‘/’인경우 .jsp로 끝나는 요청을 제외한 모든 요청을 DispatcherServlet이 처리
  • 하지만 앞선 코드에서처럼 HandlerMapping으로 @GetMapping(“/hello”) 설정을 사용하였다면, /hello 경로만 처리가능하므로 “/index.html”등의 요청을 처리할 컨트롤러를 찾을 수 없음
  • 이러한 경로를 처리하기 위한 컨트롤러 객체를 직접 구현할 수도 있지만 다음과 같이,
    WebMvcConfigurer의 configureDefaultServletHandling() 매서드를 사용하는 것이 편리
      // MvcConfig.java
      @Configuration
      @EnableWebMvc
      public class MvcConfig implements WebMvcConfigurer {
    
          @Override
          public void configureDefaultServletHandling
              (DefaultServletHandlerConfigurer configurer) 
          {
              configurer.enable();
          }
          ...
    
  • 위 설정에서 enable() 매서드는 다음의 두 Bean 객체를 추가
    • DefaultServletRequestHandler
    • SimpleUrlHandlerMapping
  • DefaultServletRequestHandler는 클라이언트의 모든 요청을 WAS가 제공하는 디폴트 서블릿에 전달

    “/index.html”에 대한 처리를 결국 디폴트 서블릿이 처리하도록 만듦

  • 그리고 SimpleUrlHandlerMapping를 이용하여 모든 경로(“/**“)를 DefaultServletHttp RequestHandler를 이용해 처리하도록 함

  • @EnableWebMvc 어노테이션이 등록하는 HandlerMapping의 적용 우선순위가 enable() 매서드가 등록하는 디폴트 핸들러보다 높음
  • 따라서 다음과 같은 방식으로 요청을 처리
    1. RequestMappingHandlerMapping을 사용해 요청 처리할 핸들러 검색
      • 존재하면 해당 컨트롤러를 이용해 요청을 처리
    2. 존재하지 않으면 SimpleUrlHandlerMapping을 사용해 요청을 처리할 핸들러 검색
      • 모든 경로에 대해 DefaultServletHttpRequestHandler를 리턴
      • DispatcherServlet은 DefaultServletHttpRequestHandler에 처리를 요청
      • DefaultServletHttpRequestHandler는 디폴트 서블릿에 처리를 위임

        예를 들어 “/index.html” 경로로 요청이 들어오면, 1과정에서 해당하는 경로를 찾지 못하므로,
        2과정을 통해 디폴트 서블릿이 /index.html 요청을 처리하게 됨