반응형
개발 공부를 할 때 과거에 어떻게 개발했는지를 아는 것은 상당한 메리트가 존재하는 것 같다.
과거에서 발생한 문제를 개선한 코드가 현재 반영되어 있기 때문에 어떠한 문제를 어떻게 해결했는지에 대해서 알 수 있으며, 현재를 이해할 수 있는 좋은 거름이 되기 때문이다. 또 레거시 프로젝트를 맡게 되었을 때 코드를 보며 빠른 이해가 가능할 뿐만 아니라 그 코드를 어떻게 개선해야 할지도 알 수 있기 때문이다.
스프링 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 |
댓글