본문 바로가기
우아한테크코스/레벨 2 - Spring

[Spring] HandlerMethodArgumentResolver 사용하기

by shyun00 2024. 5. 9.

사용자 요청을 처리할 때, 요청 데이터를 메서드의 매개변수로 받아와야하는 경우들이 있다.

(특정 헤더를 객체로 변환하거나, 쿠키나 세션에서 사용자 정보를 가져오는 등)

 

이 때 사용 가능한 것이 HandlerMethodArgumentResolver이다.

공식문서를 찾아보면 아래와 같이 설명하고 있다.

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

 

말그대로 요청에서 메서드 파라미터(데이터)를 인자값(Argument values)로 변환해주는 전략 인터페이스이다.

우리가 원하는 값을 추출하기 위해 직접 커스텀해서 사용할 수 있다.

 

요청 쿠키에서 "token" 값을 찾아 메서드 매개변수로 넣어주는 기능을 만든다고 가정해보자.

(쿠키에서 값을 추출해주는 @CookieValue가 있기는 하나, 학습을 위해 직접 작성해보기로 했다.)

 

사용자 정의 HandlerMethodArgumentResolver사용 방식은 

애너테이션 활용 방법, 클래스 사용 방법 두 가지로 구분할 수 있다.

 

애너테이션 활용 방법세 단계로 구현해볼 수 있다.

1. 애너테이션 정의

2. HandlerMethodArgumentResolver 구현 클래스 정의

    -> resolver 적용 조건을 해당 애너테이션이 있으면 동작되도록 설정

3. HandlerMethodArgumentResolver 구현 클래스 Configuration 등록

 

클래스 사용 방법은 두가지 단계로 구현할 수 있다.

1. HandlerMethodArgumentResolver 구현 클래스 정의.

    -> resolver 적용 조건을 해당 클래스이면 동작되도록 설정

2. HandlerMethodArgumentResolver 구현 클래스 Configuration 등록

 

애너테이션 사용 방법을 기준으로 설명해보고자 한다.

 

1. 애너테이션 정의

쿠키에서 토큰값을 찾아온다는 것을 명시하기 위해 @TokenValue 라는 애너테이션을 정의했다.

해당 애너테이션이 매개변수에 쓰이므로 @Target(ElementType.PARAMETER)로 설정하고,

런타임동안 유지되도록 하기 위해 @Retention(RetentionPolicy.RUNTIME)로 설정했다.

 

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenValue {
}

 

2. HandlerMethodArgumentResolver 구현 클래스 정의

HandlerMethodArgumentResolver를 구현한 클래스를 만든다.

 

supportsParameter()는 해당 메소드의 매개변수를 해당 resolver가 지원하는지 확인하는 작업을 한다.

Whether the given method parameter is supported by this resolver.

 

아래 코드를 예로 들면, 해당 파라미터에 @TokenValue 애너테이션이 있으면

true를 리턴해서 Resolver의 동작을 하고, 만약 해당 애너테이션이 없다면 false를 리턴해서 Resolver 가 적용되지 않는다.

 

만약 클래스를 기준으로 동작시키고 싶다면,

아래와 같이 파라미터 클래스 타입으로 조건을 설정해주면 된다.

컨트롤러에서 받아오는 매개변수 타입이 LoginMember이면 Resolver가 동작한다.

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

 

resolveArgument()는 실제로 매개변수를 가져오기 위한 작업을 한다.

Resolves a method parameter into an argument value from a given request. 

 

현재 코드에서는 쿠키에서 "token" 값을 가져오는 작업을 수행하고 있다.

 

public class TokenValueMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(TokenValue.class);
    }

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

    private String getTokenValue(Cookie[] cookies) {
        validateCookie(cookies);
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals("token")) {
                return cookie.getValue();
            }
        }
        return "";
    }

    private void validateCookie(Cookie[] cookies) {
        if (cookies == null) {
            throw new AuthenticationException("로그인된 회원 정보가 없습니다.");
        }
    }
}

 

3. HandlerMethodArgumentResolver 구현 클래스 등록

Resolver를 정의해도 바로 적용되는 것이 아니다.

Configuration에 등록해주어야 해당 Resolver를 사용할 수 있게 된다. 

이 때 WebMvcConfigurer를 구현한 클래스를 스프링 설정 클래스로 등록해야한다.

WebMvcConfigurer는 MVC 설정을 커스터마이징 할 수 있도록 도와주는 인터페이스이다.

(resolver 말고도 view 매핑, 인터셉터 추가, 포맷터 추가 등 여러 기능을 지원한다.)

 

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

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

 

4. 애너테이션 적용

적용 전에는 아래와 같이 HttpServletRequest에서 직접 쿠키를 뽑아와서 변환하는 로직을 수행했었다.

@GetMapping("/login/check")
public ResponseEntity<MemberResponse> getMemberInfo(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();
    MemberResponse response = memberService.getMemberInfo(cookies);

    return ResponseEntity.ok(response);
}

 

HandlerMethodArgumentResolver를  사용하면서,

아래와 같이 매개변수 앞에 @TokenValue 애너테이션을 붙여 변환 로직을 처리하도록 수정되었다.

@GetMapping("/login/check")
public ResponseEntity<MemberResponse> getMemberInfo(@TokenValue String tokenValue) {
    MemberResponse response = memberService.getMemberInfo(tokenValue);

    return ResponseEntity.ok(response);
}

 

만약 클래스 타입을 기준으로 Resolver가 동작하도록 설정했다면,

아래와 같이 코드를 작성하면 Resolver에 의해 지정된 타입으로 데이터를 받아온다.

@GetMapping("/login/check")
public ResponseEntity<MemberResponse> getMemberInfo(LoginMember loginMember) {
    MemberResponse response = memberService.getMemberInfo(loginMember);

    return ResponseEntity.ok(response);
}

 

정리

HandlerMethodArgumentResolver를 사용하기 위해서는

1. 애너테이션 정의

2. HandlerMethodArgumentResolver 구현

3. HandlerMethodArgumentResolver 구현클래스 설정파일 등록

세가지 단계를 거쳐야한다.

 

매개변수를 쉽게 가져올 수 있고, 해당 매개변수가 어떤 정보인지 명시적으로 나타낼 수 있어서 가독성도 향상된다.

또한 해당 로직이 여러 컨트롤러에서 수행될 경우 공통된 로직을 분리할 수 있어 코드가 간결해진다.

 

참고자료

HandlerMethodArgumentResolver