우리가 사용한 HttpServlet을 상속받고있는 것으로 나와있다.
스프링 부트는 DispatcherServlet
을 서블릿으로 자동등록하면서 urlPatterns = "/"
에 대하여 자동 매핑한다.
물론 더 자세히 명시를 해두면 그게 우선순위가 높게 동작하여 기존에 등록한 서블릿도 함께 동작한다.
그래서 FrameworkServlet 잘 살펴보면 service 메서드를 오버라이드하여 FrameworkServlet.sevice를 시작으로 굉장히 여러 메서드가 호출되면 결국에는 DispatcherServlet의 doDispatch() 메서드가 실행되는데 이게 핵심이다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
// 뷰 렌더링 호출
render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
// 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
DispatcherServlet
에 등록하면 우리만의 컨트롤러를 만들 수 있다.@Component("springmvc/old--controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView("new-form");
}
}
스프링 mvc의 컨트롤러는 이렇게 생겼다.
그래서 HandlerMapping이 위 이름에 맞춰서 컨트롤러를 찾고
HandlerAdapter가 핸들러 매팅을 통해 찾은 핸들러를 동작시키는데 이때 Controller 인터페이스를 통하여 편하게 구현하도록 작성되어있다.
아래는 스프링 부트가 자동으로 등록하는 핸들러 매핑과 핸들러 어댑터에 대한 설명이다. (극히 일부)
HandlerMapping
0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다. ```
HandlerAdapter
0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리
HandlerMapping에서 0순위의 RequestMappingHandlerMapping는 위 에서 작성한 컨트롤러에서는 무시된다.
왜냐하면 @Controller를 등록하지 않았기 때문이다.
그래서 HandlerAdapter에서도 0,1순위를 무시하고 2순위로 가게되는데 이 코드를 살짝 분석해보면
우리가 작성한 것과 마찬가지로 support가 되는지 와 handle이라는 메서드로 모델(뷰)을 반환하는 컨트롤러의 핸들리퀘스트가 있다. 마치 버전3와 비슷한 동작 과정이다.
@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능
에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
여기도 마찬가지로 0순위인 BeanNameViewResolver가 있고 우리는 @view를 등록하지 않았기 때문에 자동으로 다음 InternalResourceViewResolver로 넘어간다.
InternalResourceViewResolver는 InternalResourceView 를 반환한다.
그리고 InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.
view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다.
참고로 Thymeleaf에도 각각에 대응되는 리졸버와 뷰가 따로 존재한다.