Spring | HandlerMethodArgumentResolver

yeonk·2023년 5월 7일
0

spring & spring boot

목록 보기
8/10
post-thumbnail

개요


우아한테크코스 레벨2 장바구니 미션에서 HandlerMethodArgumentResolver 를 처음으로 사용해보게 되어 정리해보았다.

중복 코드 제거와 컨트롤러 내의 역할 분리를 위해 사용해보았는데, 동작 원리에 대해 아직까지는 정확한 이해는 하지 못한 것 같다.

그래서 우선 공부한 내용에 대해 정리해보고 이후에 보완하고자 한다.

주로 사용법에 대해 작성해보았고, 동작 순서나 동작 원리에 대해 100% 는 이해하진 못 했기 때문에 이에 대해서는 간단하게 정리해보았다.





HandlerMethodArgumentResolver


request에서 메소드의 parameters 값으로 변환 혹은 바인딩 하는 resolver이며, Spring framework에서 제공하는 인터페이스이다.

HandlerMethodArgumentResolver는 아래와 같은 2가지 메소드를 제공한다.

  • supportsParameter(): parameter가 해당 resolver를 지원하는 여부 확인

  • resolveArgument() : Method parameter를 argument value로 변환, 바인딩 하는 역할





동작 시점


Argument Resolver는 Interceptor 가 처리된 후 처리된다.

  1. Client 가 요청한다(Request).

  2. Dispatcher Servlet에서 Request를 처리한다.

  3. 요청을 분석하여 Request에 대한 Hadler Mapping을 한다.

    • RequestMappingHandlerAdapter(핸들러 매핑에 맞는 어댑터 결정).

    • Interceptor 처리

    • Argument Resolver 처리 (등록한 리졸버에 대응되는 파라미터 바인딩)

    • Message Converter 처리

  4. Controller Method invoke





Custom Resolver


아래의 방법을 이용하여 Custom Resolver를 사용할 수 있다.
선택적으로 구현한 Custom Resolver에 애너테이션을 사용하여 명시적으로 그 범위를 제한할 수 있다.
예시 코드는 장바구니 미션에 적용한 코드이다.





0. 애너테이션 구현

애너테이션을 통해 제한적으로 Resolver를 적용할 수 있다.
어떤 객체를 바인딩하고 있다는 것을 명시적으로 드러내기 위해 어노테이션을 사용할 수 있다.
반드시 사용해야하는 것은 아니며, 선택적으로 사용할 수 있다(생략 가능하다).

  • AuthenticationPrincipal
package cart.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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





1. Custom Resolver 구현

HandlerMethodArgumentResolver 인터페이스 구현한 클래스 생성한다.

  • AuthenticationPrincipalArgumentResolver
package cart.auth;

import org.springframework.web.method.support.HandlerMethodArgumentResolver;

public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
  // ...
}





2. supportsParameter(), resolveArgument() 구현

supportsParameter(), resolveArgument() 오버라이딩한다.

  • AuthenticationPrincipalArgumentResolver

    • supportsParameter() : 현재 파라미터를 리졸버가 지원하는지에 대한 여부에 따라 true false 값을 반환한다.
      아래 코드에서는 해당 파라미터가 @AuthenticationPrincipal 애너테이션을 포함하는지 여부에 따라 boolean 값을 리턴하고 있다.

    • resolveArgument() : 실제로 바인딩할 객체를 리턴한다.
      아래 코드에서는 request 내 Authorization 헤더에서 추출한 AuthInfo 를 리턴하고 있다.
      (바인딩: 프로그램에 사용된 구성 요소의 실제 값 또는 프로퍼티를 결정짓는 행위를 의미)

package cart.auth;

import cart.dto.auth.AuthInfo;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
    private final AuthorizationExtractor<AuthInfo> extractor = new BasicAuthorizationExtractor();
	
    // 1. supportsParameter() 구현
    @Override
    public boolean supportsParameter(final MethodParameter parameter) {
        return parameter.hasParameterAnnotation(AuthenticationPrincipal.class);
    }

	// 2. resolveArgument() 구현
    @Override
    public AuthInfo resolveArgument(final MethodParameter parameter, final ModelAndViewContainer mavContainer,
                                  final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        return extractor.extract(request);
    }
}





3. 구현한 resolver 추가

WebMvcConfigurer 를 구현한 클래스에 addArgumentResolver() 메소드로 구현한 resolver 추가한다.
아래와 같이 적용이 필요한 파라미터에 @AuthenticationPrincipal를 붙여 사용할 수 있다.

  • CartConfiguration
package cart.config;

import cart.auth.AuthenticationPrincipalArgumentResolver;
import cart.auth.LoginInterceptor;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CartConfiguration implements WebMvcConfigurer {

// ... viewControllers, Interceptor 관련 코드 ...

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



  • CartApiController
package cart.controller;

import cart.auth.AuthenticationPrincipal;
import cart.dto.CartItemDto;
import cart.dto.auth.AuthInfo;
import cart.service.CartService;
import java.net.URI;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cart")
@RequiredArgsConstructor
public class CartApiController {

    private final CartService cartService;

// ...

    @DeleteMapping("/{cartId}")
    public ResponseEntity<Void> delete(@PathVariable long cartId, @AuthenticationPrincipal AuthInfo authInfo) {
        cartService.delete(authInfo.getEmail(), cartId);
        return ResponseEntity
                .ok()
                .build();
                
// ...

    }





HandlerMethodArgumentResolver 동작 원리


InvocableHandlerMethod

InvocableHandlerMethod: Controller의 메서드를 wrapping

Spring은 InvocableHandlerMethodinvokeForRequest()Request를 처리한다.

invokeForRequest() 내에서 호출되는 getMethodArgumentValues() 내의 resolversHandlerMethodArgumentResolverComposite 객체이다.

HandlerMethodArgumentResolverComposite 를 살펴보자.





HandlerMethodArgumentResolverComposite

HandlerMethodArgumentResolverCompositeHandlerMethodArgumentResolver를 상속받은 객체이다.

HandlerMethodArgumentResolverComposite은 다른 모든 HandlerMethodArgumentResolver를 상속받은 객체를 가지고 루프를 돌며 지원하는 파라미터인지 확인을 하고 결과를 리턴한다.

이 때 지원하는 파라미터인지 확인 하는 역할을 담당하는 것이 위에서 다룬 supportsParameter() 이다.





정리


파라미터로 받는 값이 많고, 중복이 발생할 때 공통적인 처리를 위해 HandlerMethodArgumentResolver를 사용하면 좋을 것 같다.





참고 자료


Interface HandlerMethodArgumentResolver

Spring HandlerMethodArgumentResolver의 사용법과 동작원리

HandlerMethodArgumentResolver 내부동작 원리 알아보기

HandlerMethodArgumentResolver 동작 원리

0개의 댓글