관리 메뉴

Rootable의 개발일기

요청 매핑 핸들러 어뎁터 구조 본문

Spring

요청 매핑 핸들러 어뎁터 구조

dev-rootable 2023. 7. 18. 16:41

📌 개요

 

스프링 MVC 구조

 

스프링 MVC 구조에서 요청이 들어오면 가장 먼저 Dispatcher Servlet에서 요청과 매핑되는 핸들러를 찾는다. 핸들러를 실행하는 주체는 HandlerAdapter다. 그래서 Dispatcher Servlet은 매핑된 핸들러를 실행해 줄 HandlerAdapter를 목록에서 찾는 것이다.

 

핸들러(컨트롤러) 종류에 따라 호출되는 HandlerAdapter가 달라지는데, 우리는 @RequestMapping 또는 이를 간략화한 @GetMapping, @PostMapping 등이 붙은 핸들러를 많이 사용한다. 이러한 애노테이션 기반의 핸들러를 실행해 주는 HandlerAdapter를 RequestMappingHandlerAdapter라고 한다.

 

요청 매핑 핸들러 어뎁터 == RequestMappingHandlerAdapter

 

결과적으로 RequestMappingHandlerAdapter에서 HTTP 요청을 읽고 원하는 응답을 내리도록 하는 것이다. 이것을 자세히 살펴보자.

 

HTTP 메시지 컨버터에 대한 자세한 내용은 아래의 지난 글에서 확인할 수 있다.

 

https://dev-rootable.tistory.com/78

 

HTTP 메시지 컨버터(HTTP Message Converter)

📌 역할 1. HTTP 메시지 바디의 내용을 우리가 읽을 수 있도록 객체 또는 문자로 변환해 준다. 2. HTTP 메시지 바디에 우리가 생성한 데이터를 적을 수 있도록 한다. 3. HTTP 메시지 컨버터는 지원하는

dev-rootable.tistory.com

 

📌 동작 방식

 

컨트롤러에서 @RequestBody, @RequestParam, HttpEntity 등 다양한 파라미터를 사용할 수 있다. 이러한 파라미터들이 제 기능을 하기 위해서는 요청 데이터를 서버로 가져올 때 알아볼 수 있도록 변환해야 한다. 이 역할을 하는 것이 바로 ArgumentResolver(HandlerMethodArgumentResolver)다.

 

@RequestBody나 HttpEntity를 통해 InputStream 없이 HTTP 메시지 바디의 내용을 읽을 수 있도록 해준 HTTP 메시지 컨버터는 ArgumentResolver에서 호출한 것이다.

 

ArgumentResolver는 컨트롤러가 필요로 하는 다양한 파라미터 값(객체)를 생성

 

출처: 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 by 김영한

 

📌 핸들러 어뎁터의 @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()호출

 

 - 핸들러 실행, 반환 타입 설정들을 결정

 

ServletInvocableHandlerMethod

 

5. invokeAndHandle()에서 invokeForRequest()를 호출하면 getMethodArgumentValues() 메서드가 실행되고, 여기서 supportsParameter()를 통해 해당 파라미터를 처리할 수 있는 resolver를 조회

 

 - @RequestBody를 처리할 수 있는 ArgumentResolver는 RequestResponseBodyMethodProcessor

 - HttpEntity를 처리할 수 있는 ArgumentResolver는 HttpEntityMethodProcessor

 

 

getMethodArgumentValues 메서드

 

HandlerMethodArgumentResolverComposite

 

5. 지원한다면 try 문의 resolveArgument()를 실행

 

HandlerMethodArgumentResolverComposite

 

6. RequestResponseBodyMethodProcessor의 readWithMessageConverters()를 실행하여 요청 데이터 타입에 맞는 컨버터를 얻어온다.

 

 - String 타입이므로 StringHttpMessageConverter 동작

 

RequestResponseBodyMethodProcessor

 

readWithMessageConverters 메서드

 

 

 

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

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

www.inflearn.com

 

https://jwdeveloper.tistory.com/306

 

(SERVLET) @RequestBody는 어떻게 동작할까?

@RequestBody는 setter 메서드가 없더라도 요청한 json 데이터를 DTO에 매핑하여 응답 본문을 작성한다. 이것이 가능한 이유를 디버깅을 통해 알아보자! DispatcherServlet에서 시작한다. 요청한 json 데이터

jwdeveloper.tistory.com

 

https://duooo-story.tistory.com/9

 

[Spring MVC] @ResponseBody 응답에 대하여 알아보자(1)

API를 만들때, Json형태의 응답값을 내려주기 위하여 @ResponseBody 라는 어노테이션을 사용합니다. 이 어노테이션을 사용하면 본문 자체를 응답값으로 내려주기에 유용하게 사용하고 있지만. 정확하

duooo-story.tistory.com

 

'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