Rootable의 개발일기
요청 매핑 핸들러 어뎁터 구조 본문
📌 개요
스프링 MVC 구조에서 요청이 들어오면 가장 먼저 Dispatcher Servlet에서 요청과 매핑되는 핸들러를 찾는다. 핸들러를 실행하는 주체는 HandlerAdapter다. 그래서 Dispatcher Servlet은 매핑된 핸들러를 실행해 줄 HandlerAdapter를 목록에서 찾는 것이다.
핸들러(컨트롤러) 종류에 따라 호출되는 HandlerAdapter가 달라지는데, 우리는 @RequestMapping 또는 이를 간략화한 @GetMapping, @PostMapping 등이 붙은 핸들러를 많이 사용한다. 이러한 애노테이션 기반의 핸들러를 실행해 주는 HandlerAdapter를 RequestMappingHandlerAdapter라고 한다.
요청 매핑 핸들러 어뎁터 == RequestMappingHandlerAdapter
결과적으로 RequestMappingHandlerAdapter에서 HTTP 요청을 읽고 원하는 응답을 내리도록 하는 것이다. 이것을 자세히 살펴보자.
HTTP 메시지 컨버터에 대한 자세한 내용은 아래의 지난 글에서 확인할 수 있다.
https://dev-rootable.tistory.com/78
📌 동작 방식
컨트롤러에서 @RequestBody, @RequestParam, HttpEntity 등 다양한 파라미터를 사용할 수 있다. 이러한 파라미터들이 제 기능을 하기 위해서는 요청 데이터를 서버로 가져올 때 알아볼 수 있도록 변환해야 한다. 이 역할을 하는 것이 바로 ArgumentResolver(HandlerMethodArgumentResolver)다.
@RequestBody나 HttpEntity를 통해 InputStream 없이 HTTP 메시지 바디의 내용을 읽을 수 있도록 해준 HTTP 메시지 컨버터는 ArgumentResolver에서 호출한 것이다.
ArgumentResolver는 컨트롤러가 필요로 하는 다양한 파라미터 값(객체)를 생성
📌 핸들러 어뎁터의 @RequestBody와 @ResponseBody 처리 과정
@RequestBody와 @ResponseBody의 요청 및 응답 처리 과정을 알아보자.
🔎 ArgumentResolver 인터페이스
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
ArgumentResolver는 supportsParameter를 통해 해당 타입을 지원하는 ArgumentResolver를 탐색
지원할 경우 resolveArgument를 호출하여 실제 객체를 생성한다. 그리고 이렇게 생성된 객체가 컨트롤러 호출시 넘어가는 것이다.
🔎 요청 처리 과정
위 컨트롤러가 동작하는 방식은 다음과 같다.
1. HTTP 요청을 DispatcherServlet에서 받음
2. DispatcherServlet의 doDispatch() 메서드를 실행하여 해당 핸들러를 처리할 수 있는 핸들러 어뎁터를 획득
3. doDispatch() 내 handle() 메서드를 호출하여 핸들러 어뎁터 실행
4. 핸들러 어뎁터가 실행되면서 RequestMappingHandlerAdapter에서 invocableMethod.invokeAndHandle()호출
- 핸들러 실행, 반환 타입 설정들을 결정
5. invokeAndHandle()에서 invokeForRequest()를 호출하면 getMethodArgumentValues() 메서드가 실행되고, 여기서 supportsParameter()를 통해 해당 파라미터를 처리할 수 있는 resolver를 조회
- @RequestBody를 처리할 수 있는 ArgumentResolver는 RequestResponseBodyMethodProcessor
- HttpEntity를 처리할 수 있는 ArgumentResolver는 HttpEntityMethodProcessor
5. 지원한다면 try 문의 resolveArgument()를 실행
6. RequestResponseBodyMethodProcessor의 readWithMessageConverters()를 실행하여 요청 데이터 타입에 맞는 컨버터를 얻어온다.
- String 타입이므로 StringHttpMessageConverter 동작
7. 파라미터의 값(객체)이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
🔎 ReturnValueHandler 인터페이스
ArgumentResolver와 비슷한 역할을 하는 ReturnValueHandler(HandlerMethodReturnValueHandler)는 응답 값을 변환하고 생성하는 역할을 한다.
스프링은 10여 개가 넘는 ReturnValueHandler를 지원한다.
public interface HandlerMethodReturnValueHandler {
//해당 타입을 지원하는 ArgumentResolver 조회
boolean supportsReturnType(MethodParameter returnType);
//지원할 경우 응답 객체 생성
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
🔎 응답 처리 과정
1. DispatcherServlet의 doDispatch() 메서드를 실행하여 해당 핸들러를 처리할 수 있는 핸들러 어뎁터를 획득
2. doDispatch() 내 handle() 메서드를 호출하여 핸들러 어뎁터 실행
3. 핸들러 어뎁터가 실행되면서 RequestMappingHandlerAdapter에서 invocableMethod.invokeAndHandle() 호출
- 핸들러 실행, 반환 타입 설정들을 결정
4. try문의 handleReturnValue()가 실행되면서 핸들러를 선택하기 위해 selectHandler() 호출
5. selectHandler()를 통해 supportsReturnType()이 실행되면 HandlerMethodReturnValueHandler 중에서 @ResponseBody와 매칭되는 RequestResponseBodyMethodProcessor를 사용
6. RequestResponseBodyMethodProcessor의 handleReturnValue() 메서드를 통해 응답 객체 생성
7. writeWithMessageConverters()를 실행하면 응답 데이터 타입에 맞는 컨버터를 얻어온다.
- String 타입이므로 StringHttpMessageConverter 동작
8. 마지막으로 다시 RequestMappingHandlerAdapter로 돌아와서 null 반환 후 종료
- 뷰 렌더링 없음
🔎 ArgumentResolver 상속 관계도
💡 RequestResponseBodyMethodProcessor
@RequestBody와 @ResponseBody를 모두 지원하는 ArgumentResolver로,
HandlerMethodArgumentResolver와 HandlereMethodReturnValueHandler 기능 둘 다 구현한 것이다.
📌 확장
스프링은 HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler를 모두 인터페이스로 제공하므로 필요하면 언제든지 확장할 수 있다.
🔎 예시
세션의 회원 정보를 원하는 컨트롤러에서 가져올 수 있는 MyArgumentResolver 생성하기
1. WebMvcConfigurer를 구현한 클래스에 자신이 만든 ArgumentResolver를 등록해야 한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver());
}
}
2. A라는 커스텀 애노테이션 생성
3. MyArgumentResolver implements HandlerMethodArgumentResolver
- supportsParameter() 메서드 구현: 어떤 애노테이션을 붙였을 때 또는 어떤 타입일 때 허용할지 결정
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean hasAnnotation = parameter.hasParameterAnnotation(A.class); //@A가 붙었을 때
boolean hasType = B.class.isAssignableFrom(parameter.getParameterType()); //B 타입 파라미터
return hasLoginAnnotation && hasMemberType;
}
- resolveArgument() 메서드 구현: 세션의 회원 정보를 반환하면 원하는 기능을 구현할 수 있다.
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
HttpSession session = request.getSession();
if (session == null) {
return null;
}
return session.getAttribute("HttpSession에 저장된 세션 이름");
}
Reference:
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
https://jwdeveloper.tistory.com/306
https://duooo-story.tistory.com/9
'Spring' 카테고리의 다른 글
스프링 MVC 검증 - Bean Validation (0) | 2023.07.22 |
---|---|
스프링 MVC 검증 - Validation (0) | 2023.07.22 |
HTTP 메시지 컨버터(HTTP Message Converter) (0) | 2023.07.17 |
HTTP 요청 파라미터 (0) | 2023.07.15 |
HTTP 요청 매핑 (0) | 2023.07.15 |