HandlerMethodArgumentResolver 란

Lily·2024년 5월 18일
7
post-thumbnail

들어가며

아래는 Spring 에서 요청 데이터를 받아 원하는 객체를 반환하는 역할을 수행하는 예시이다.

@GetMapping("/check")
    public MemberResponse findUser(HttpServletRequest request) {
        TokenResponse tokenResponse = authService.extractTokenByCookies(request);
        String email = authService.extractEmailByToken(tokenResponse);
        return loginMemberService.findNameByEmail(email);
  • 로그인을 확인하는 API 에서 쿠키에 담겨있는 토큰을 이용해 검증을 한 후 Member 객체를 반환하고 있다.
    (예시는 예시일 뿐 service 에서 cookie 를 받는 💩 같은 로직은 무시하고 봐주십시오 .. 예전 코드를 가져와서 디벨롭이 안되어 있는 상태 ㅎㅅㅎㅅㅎ)

By default, Spring only knows how to convert simple types. In other words, once we submit data to controller Int, String or Boolean type of data, it will be bound to appropriate Java types automatically.
But in real-world projects, that won’t be enough, as we might need to bind more complex types of objects.

위의 이유 때문에 우리는 Member 객체를 바인딩 하기 위해서 해당 작업이 필요한 API 마다 같은 작업을 수행해야 한다.

이는 컨트롤러에 중복 코드가 늘어날 뿐만 아니라, 컨트롤러의 책임이 더 늘어나게 된다는 단점이 있다.

이것을 해결할 수 있는 스프링의 데이터 바인딩 메커니즘이 있다.

HandlerMethodArgumentResolver

Spring 에서 HandlerMethodArgumentResolver 는 요청 데이터를 메서드의 매개변수로 변환할 때 사용하는 전략 인터페이스이다.

다시 말해서, 어떠한 요청이 컨트롤러에 들어왔을 때 요청 데이터로부터 원하는 객체를 반환하는 역할을 수행한다.

Strategy interface for resolving method parameters into argument values in the context of a given request.

아래는 HandlerMethodArgumentResolver 를 implement 받아 custom MemberArgumentResolver 를 구현한 예시이다.

MemberRequest 를 반환하는 역할을 resolveArgument() 가 수행하고 있다.

(참고) 컨트롤러가 도메인에 접근하지 못하도록 Member (domain) 대신 MemberRequest (DTO) 를 사용했다.

@Component
public class MemberArgumentResolver implements HandlerMethodArgumentResolver {
    private final AuthService authService;

    public MemberArgumentResolver(AuthService authService) {
        this.authService = authService;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(MemberRequest.class);
    }

    @Override
    public MemberRequest resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                         NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
            throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        Cookie[] cookies = request.getCookies();
        return authService.extractMemberByCookies(cookies);
    }
}

HandlerMethodArgumentResolver‘s resolveArgument() method returns an Object. In other words, we could return any object, not only String.

또한 HandlerMethodArgumentResolver 의 function 을 @Override 하여 구현한 것을 알 수 있다.

공식문서를 살펴보면,

HandlerMethodArgumentResolver 의 Function 은 총 2개이다.

supportsParameter

주어진 메서드 매개변수가 이 리졸버에서 지원되는지 여부를 반환한다.

Whether the given MethodParameter is supported by this resolver.

메서드 시그니처를 살펴보자!

boolean supportsParameter(MethodParameter parameter)
  • parameter - the method parameter to check
    📌 설명 : 확인할 메서드 매개변수

resolveArgument

주어진 요청 데이터로부터 메서드 매개변수를 해석한다.

Resolves a method parameter into an argument value from a given request. A ModelAndViewContainer provides access to the model for the request. A WebDataBinderFactory provides a way to create a WebDataBinder instance when needed for data binding and type conversion purposes.

메서드 시그니처를 살펴보자!

@Nullable
Object resolveArgument(MethodParameter parameter,
 @Nullable
 ModelAndViewContainer mavContainer,
 NativeWebRequest webRequest,
 @Nullable
 WebDataBinderFactory binderFactory)
                throws Exception
  • parameter - the method parameter to resolve. This parameter must have previously been passed to supportsParameter(org.springframework.core.MethodParameter) which must have returned true.
    📌 설명 : 확인할 메서드 매개변수 - supportsParameter 에서 true 를 반환했어야 함

  • mavContainer - the ModelAndViewContainer for the current request
    📌 설명 : 현재 요청에 대한 ModelAndViewContainer - ModelAndViewContainer는 모델에 대한 액세스와 데이터 바인딩 및 type conversion 을 위해 필요할 때 WebDataBinder 인스턴스를 생성하는 방법을 제공함

  • webRequest - the current request

  • binderFactory - a factory for creating WebDataBinder instances
    📌 설명 : WebDataBinder 인스턴스를 생성하기 위한 팩토리


이제 MemberArgumentResolver 구현을 마쳤다!
그런데 구현만 해놓는다고 해서 스프링이 자동으로 리졸버를 사용하는게 아니다.

WebMvcConfigurer

스프링이 search 할 위치를 알려주기 위해서 WebMvcConfigurer 를 이용하면 된다.

아래는 WebMvcConfigurer 를 implement 받아 WebMvcConfig 를 구현한 예시이다.

  • addArgumentResolvers() 를 오버라이드 하여 구현하면 된다.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    private final MemberArgumentResolver memberArgumentResolver;
    
    public WebMvcConfig(MemberArgumentResolver memberArgumentResolver) {
        this.memberArgumentResolver = memberArgumentResolver;
       
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(memberArgumentResolver);
    }
}

현재 MemberArgumentResolver 가 @Component 로 빈으로 관리되고 있기 때문에 새로운 객체를 생성하는 대신 생성자를 통해 주입받아 사용해주었다.

이제 다 되었다!
컨트롤러에서 사용이 가능하게 되었다.

아까 살펴본 로그인 check API 에서 어떻게 달라질까?

  • 원래 코드
@GetMapping("/check")
    public MemberNameResponse findUser(HttpServletRequest request) {
        TokenResponse tokenResponse = authService.extractTokenByCookies(request);
        String email = authService.extractEmailByToken(tokenResponse);
        return loginMemberService.findNameByEmail(email);
  • 바뀐 코드
@GetMapping("/check")
public MemberNameResponse findUser(MemberRequest memberRequest) {
        return new MemberNameResponse(memberRequest.getName());
    }

로그인을 확인하는 API 에서 resolver 를 통해 검증이 완료된 MemberRequest 객체를 받아와 로직이 더 깔끔해졌다 :)

Reference

A Custom Data Binder in Spring MVC
Interface HandlerMethodArgumentResolver
HandlerMethodArgumentResolver

profile
내가 하고 싶은 거

4개의 댓글

comment-user-thumbnail
2024년 5월 18일

MemberArgumentResolver는 MemberRequest를 반환하는데 바뀐 코드에선 Member를 받네요

2개의 답글
comment-user-thumbnail
2024년 5월 20일

The addictive nature of Slope Game made me unable to take my eyes off the screen.

답글 달기

관련 채용 정보