본문 바로가기
Spring|Spring-boot

핸들러 매핑과 핸들러 어댑터

by oncerun 2021. 12. 25.
반응형

개발 공부를 할 때 과거에 어떻게 개발했는지를 아는 것은 상당한 메리트가 존재하는 것 같다.

과거에서 발생한 문제를 개선한 코드가 현재 반영되어 있기 때문에 어떠한 문제를 어떻게 해결했는지에 대해서 알 수 있으며, 현재를 이해할 수 있는 좋은 거름이 되기 때문이다. 또 레거시 프로젝트를 맡게 되었을 때 코드를 보며 빠른 이해가 가능할 뿐만 아니라 그 코드를 어떻게 개선해야 할지도 알 수 있기 때문이다.

 

스프링 MVC 구조를 보면 FrontController 패턴과 handler를 찾는 과정,  handler를 처리할 수 있는 Adapter를 찾는 과정과 찾은 Adpater로 사용자가 정의한 비즈니스 로직을 실행하는 handle() 메서드와  메서드의 결과로 ModelAndView를 반환하여 ViewResolver에게 View를 찾고 클라이언트에게 전달해 주도록 설계가 되어있다.

 

구성 요소 설명
DispatcherServlet 클라이언트의 요청을 전달받는다. Controller에게 클라이언트의 요청을 전달하고, Controller가 리턴한 결과값을 View에 전달하여 알맞은 응답을 생성하도록 한다.
HandlerMapping 클라이언트의 요청 URL을 어떤 Controller가 처리할지를 결정한다.
HandlerAdapter Controller를 처리할 수 있는 Adapter를 찾아서 반환해 준다.
Controller 클라이언트의 요청을 처리한 뒤, 그 결과를 DispatcherServlet에 알려준다. 
ViewResolver Commander의 처리 결과를 보여줄 View를 결정한다.
View Commander의 처리 결과를 보여줄 응답을 생성한다.

 

그중 핸들러 매핑과 핸들러 어댑터에 대해서 공부한다.

 

Controller 

 

과거 스프링 MVC에서 Controller를 구현하기 위해서 다음과 같은 코드가 필요했다.

@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return null;
    }
}

 

이때 implements Controller의 패키지는 org.springframework.web.serblet.mvc.Controller 임을 유의해야 한다. 

 

클라이언트의 HTTP 요청에 대해서 Dispatcher Servlet은 요청을 처리할 컨트롤러를 핸들러 맵핑에서 찾아야 한다.

이때 스프링 빈의 이름으로 핸들러를 찾야 하기 때문에 @Component의 빈 이름으로 urlMapping을 하였다.

 

그 이후에는 interface Controller를 실행할 수 있는 Adapter를 찾아와야 한다.

@FunctionalInterface
public interface Controller {

   /**
    * Process the request and return a ModelAndView object which the DispatcherServlet
    * will render. A {@code null} return value is not an error: it indicates that
    * this object completed request processing itself and that there is therefore no
    * ModelAndView to render.
    * @param request current HTTP request
    * @param response current HTTP response
    * @return a ModelAndView to render, or {@code null} if handled directly
    * @throws Exception in case of errors
    */
   @Nullable
   ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

 

스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현하여 개발자가 직접 핸들러 매핑과 핸들러 어댑터를 구현할 일은 별로 없다.

 

HandlerMapping

 

클라이언트 요청을 DispatcherServlet이 처리하도록 설정은 Spring-Boot를 사용하면 기본적으로 "/"로 설정이 되고

spring인 경우 web.xml에서 <servlet-mapping>하는 곳에서 <url-pattern>을 통해 지정한다.

 

스프링 MVC는 다형성을 매우 잘 활용하여서 FrontController가 HandlerMapping의 인터페이스를 의존하고 있다.

그렇기에 HandlerMapping의 구현체의 DI를 통해 코드 변경 없이 클라이언트 요청을 처리할 수 있는 핸들러를 찾는 다양한 구현체들만의 방법을 사용할 수 있다.

구현체 설명
BeanNameUrlHandlerMapping 요청 URI와 동일한 이름을 가진 Controller 빈을 매핑한다. 
이 방법이 위 예시코드에서 사용된 HandlerMapping 구현체이다.
SimpleUrlHandlerMapping Ant 스타일의 경로 매핑 방식을 사용하여 URI와 Controller 빈을 매핑한다.
RequestMappingHandlerMapping 애노테이션 기반의 컨트롤러인 @ReqeustMapping에서 사용된다.

 

스프링을 사용한다면 web.xml에 설정한 dispatcherServlet에서 contextConfigLocation를 찾아 설정 파일에

사용할 핸들러 매핑의 구현체를 빈으로 등록하면 된다.

 

스프링 부트인 경우는 자동으로 여러 개의 핸들러 매핑 구현체를 등록해주는데  두 가지를 소개한다.

구현체 우선순위 설명
RequestMappingHandlerMapping 0 위 설명과 동일하다. @Controller 혹은 @RequestMapping, @GetMapping등 요청 url을 처리할 컨트롤러를 어노테이션이 붙은 컨트롤러와 일치하는지 검색하여 반환한다.
BeanNameUrlHandlerMapping 1 어노테이션이 붙은 핸들러 중 클라이언트 요청 URI을 처리할 핸들러(컨트롤러)가 존재하지 않으면 빈 이름 중 요청 URI와 일치하는 핸들러를 찾는다.

 

HandlerAdapter

요청을 처리할 핸들러를 찾았다면 그 핸들러가 가지고 있는 interface 타입을 처리할 Adapter가 필요하기 때문에 

해당 어댑터를 찾는 과정이 필요하다.

 

구현체 우선순위 설명
RequestMappingHandlerAdapter 0 찾은 handler가 애노테이션 기반 컨트롤러인 경우
HttpRequestHandlerAdapter 1 HttpRequestHandler 인 경우이며 서블릿과 가장 유사하게 구현되어 있다.
SimpleControllerHandlerAdapter 2 Controller interface 타입인 경우
public class SimpleControllerHandlerAdapter implements HandlerAdapter {

   @Override
   public boolean supports(Object handler) {
      return (handler instanceof Controller);
   }

   @Override
   @Nullable
   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {

      return ((Controller) handler).handleRequest(request, response);
   }

   @Override
   @SuppressWarnings("deprecation")
   public long getLastModified(HttpServletRequest request, Object handler) {
      if (handler instanceof LastModified) {
         return ((LastModified) handler).getLastModified(request);
      }
      return -1L;
   }

}

 

스프링 MVC는 사용할 수 있는 HandlerAdapter를 반복문을 통해 supports를 호출한다. 

해당 과정 중에 instanceof가 자신의 타입과 일치하면 adapter로 사용될 수 있음을 의미하고 해당 어댑터가 반환된다.

 

SimpleControllerHandlerAdapter가 과거에 사용되었던 핸들러이며, OldController가 implement 한 Controller는 당연히 handleRequest(HttpServletRequest request, HttpServletResponse response)를 구현하고 있다고 약속되어 있다.

또 Controller는 ModelAndView를 반환한다. 

찾은 어댑터의 handle메서드를 통해 컨트롤러의 로직이 담겨있는 handleRequest가 실행되고 그 결과로 ModelAndView객체를 받아 반환한다. 

 

 

결과

우리가 예시로 들었던 OldController를 실행하기 위해서|
DispatcherServlet은 다음의 핸들러 매핑과 핸들러 어댑터를 사용했다.

 

HandlerMapping은 BeanNameUrlHandlerMapping을 사용해 요청을 처리할 컨트롤러를 찾았고 

HandlerAdapter는 SimpleControllerhandlerAdapter를 사용해 요청을 처리하고 그 결과를 반환했다.

 

 

반응형

'Spring|Spring-boot' 카테고리의 다른 글

Spring MVC HTTP Header 처리와 Arguments, Return  (0) 2021.12.29
뷰 리졸버  (0) 2021.12.25
싱글톤과 함께 사용하는 프로토타입 문제점  (0) 2021.12.18
Spring BeanDefinition  (0) 2021.12.15
Validation  (0) 2021.06.10

댓글