핸들러 매핑과 핸들러 어댑터들이 어떤것들이 스프링 MVC에서 사용되는지 알아보겠습니다.
지금은 애노테이션 열풍이 불고 있으나,
과거에 주로 사용했던 스프링이 주로 사용했던 간단한 컨트롤러로 핸들러 매핑과 어댑터에 대해 설명해보겠습니다.
@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;
}
package org.springframework.web.servlet.mvc;
스프링 MVC에서 과거에 주로 사용했던 컨트롤러 중 하나를 긁어왔습니다.
보면 ModelAndView를 반환하는 것으로 보아 V3 버전과 유사한것을 알 수 있습니다.
참고로 Controller 클래스는 @Controller 애노테이션과는 완전히 다른 방식의 컨트롤러임을 기억합시다.
package hello.servlet.web.springmvc.old;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
// StereoType의 컨트롤러는 애노테이션
@Component("/springmvc/old-controller") // spring bean의 이름을 urlPattern에 맞춤
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
System.out.println("OldController.handleRequest");
return null;
}
}
implements 할때 유의할 점이 StereoType에 속한 Controller 클래스는 애노테이션 관련 컨트롤러 클래스이므로 우리가 작성할때는 이전에 패키지에서 명시했듯 MVC 패키지 내의 Controller 객체를 구현해야한다는 점입니다.
@Component를 통해 이 OldController 클래스는 그 뒤에 명시된 문자열로 urlPatterns가 등록된다.
빈의 이름으로 url이 매핑되는 것으로 이해하자, 저 빈의 이름은 /springmvc/old-controller가 되는 것이다.
이 컨트롤러가 호출되는 방식은 어떻게 되는 것인가?
먼저 스프링 MVC의 처리 구조를 확인하자.
이 컨트롤러가 호출되기 위해서는 다음 두 가지가 필요하다
1) 핸들러 매핑 - 핸들러 매핑 정보로 이 빈이 등록될 수 있어야한다
즉, 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다
private void initHandlerMappingMap() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/", new MemberListControllerV3());
// V4 컨트롤러 추가
handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members/", new MemberListControllerV4());
}
우리가 짠 MVC를 기준으로 이 코드에 어떻게 저 빈의 이름을 가져다가 Key, Value의 형태로 넣을 것인지 생각해봐야한다.
2) 핸들러 어댑터
- 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다 .handle() 메소드
Controller 인터페이스를 찾아서 실행할 수 있는 (.service()) 핸들러 어댑터를 찾고 실행해야 한다.
스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 이미 구현해두었다고 한다.
HandlerMapping (우선순위)
0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
HandlerAdapter (우선순위)
0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리
현재 스프링은 애노테이션 방식의 @RequestMapping을 가장 많이 사용하고 있다
핸들러 매핑으로 핸들러 조회
1) HandlerMapping을 순서대로 실행하여, 핸들러를 찾습니다
2) 이 경우 빈 이름으로 핸들러를 찾아야하기 때문에 이름 그대로
빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping이 실행에 성공하고 핸들러인 OldController를 반환합니다.
2) 핸들러 어댑터 조회
Handler Adapter의 supports() 메서드를 순서대로 호출합니다.
HttpRequestHandlerAdapter는 핸들러인 MyHttpRequestHandler를 내부에서 실행하고 그 결과를 반환합니다.
-> Adapter 객체에서 handler() 실행한다는 뜻
3) 핸들러 어댑터 실행
DispatcherServlet(프론트 컨트롤러)에서 조회한 HttpRequestHandlerAdapter를 실행하면서, 핸들러 정보도 함께 넘겨준다.
HttpRequestHandlerAdapter는 핸들러인 MyHttpRequestHandler를 내부에서 실행하고, 그 결과를 반환한다.
-> 결과는 V에 따라 ModelAndView일수도 MyView처럼 View만 반환할수도 있다.
정리 - MyHttpRequestHandler를 실행하면서 사용된 객체는 다음과 같다.
HandlerMapping = BeanNameUrlHandlerMapping
HandlerAdapter = HttpRequestHandlerAdapter
뷰 리졸버에 대해 알아보자
OldController 코드를 변경하여 뷰 리졸버 동작방식에 대해서 알아보도록 하겠다
package hello.servlet.web.springmvc.old;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
// SterpType의 컨트롤러는 애노테이션
@Component("/springmvc/old-controller") // spring bean의 이름을 urlPattern에 맞춤
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
System.out.println("OldController.handleRequest");
return new ModelAndView("new-form");
}
}
이때 코드를 그냥 실행하면 WhiteLabelError페이지 오류가 발생한다.
스프링의 application.properties에 다음과 같은 코드를 추가하자.
logging.level.org.apache.coyote.http11=debug
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
뷰 리졸버는 InternalResourceViewResolver를 사용한다 (스프링 기본값)
다음과 같이 prefix와 suffix를 등록하여 사용한다.
뷰 리졸버 동작방식
스프링 부트가 자동으로 등록하는 뷰 리졸버에는 두가지가 있다
1 = BeanNameViewResolver(빈 이름으로 뷰를 찾아서 반환한다)
2= InternalResourceViewResolver(JSP를 처리할 수 있는 뷰를 반환한다)
실제 호출과정은 다음과 같다.
1) 핸들러 어댑터 호출
핸들러 어댑터를 통해 new-form이라는 논리 뷰 이름을 획득한다.
2) ViewResolver 호출
new-form이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.
BeanNameViewResolver는 new-form이라는 스프링 빈으로 등록된 뷰를 찾아야하나 없다.
- 커스텀 뷰 객체를 사용해야하는 경우 사용함
InternalResourceViewResolver가 호출된다.
3) InternalResourceViewResolver
이 뷰 리졸버는 InternalResourceView를 반환한다
4) 뷰 - InternalResourceView
InternalResourceView는 JSP처럼 forward()를 호출해서 처리할 수 있는 경우에 사용한다.
5) view.render()
view.render()가 호출되고 InternalResourceView는 forward()를 사용해서 JSP를 실행한다.
나머지 뷰 템플릿들은 forward 과정 없이 바로 렌더링이 가능하다 (자바 코드로 렌더링)
그림을 약간 수정할 필요가 있겠다, 핸들러 내용이 컨트롤러에서 뷰로 넘어가는데 그렇다면 위의 그림에서 뷰 리졸버와 프론트 컨트롤러 사이에는 어댑터 그림이 하나 추가되어야 할 필요가 있겠다.
'스프링 공부 (인프런 김영한 선생님) > 스프링 MVC 1편' 카테고리의 다른 글
[스프링 웹 MVC 1편] 19. 스프링 MVC - 기본 기능 (로깅) (0) | 2023.06.01 |
---|---|
[스프링 웹 MVC 1편] 18. 스프링 MVC - 시작 (0) | 2023.05.30 |
[스프링 웹 MVC 1편] 16. 스프링 MVC - V1 ~ V5와 비교했을 때 (구조 이해) (0) | 2023.05.29 |
[스프링 웹 MVC 1편] 15. MVC 프레임워크 제작해보기 - v5 (0) | 2023.05.23 |
[스프링 웹 MVC 1편] 14. MVC 프레임워크 제작해보기 - v4 (0) | 2023.05.23 |