[Spring] Resolver 란? Resolver 구현하기(HandlerMethodArgumentResolver)

🙆‍♂️ import 🙇‍♂️

Custom HandlerMethodArgumentResolver 만들어보기[Advenoh - FRANK OH]

Spring Argument Resovler[jaehun2841.github.io]


HandlerMethodArgumentResolver

HandlerMethodArgumentResolverInterface로써,

ControllerArgument(Parameter)에 지정된 변수들을,

Annotation이나 객체의 Type에 따라Resolver를 먼저 거쳐,

실제 Data를 Controller에 넘겨주는 역할을 수행한다.


Controller에 들어오는 Argument(Parameter)를 가공(암호화 > 복호화) 하거나,

Argument(Parameter)를 추가하거나 수정해야 하는 경우에 사용한다.


실제 해당 Interface의 형태는 아래와 같다.

/**
 * Strategy interface for resolving method parameters into argument values in
 * the context of a given request.
 *
 * @author Arjen Poutsma
 * @since 3.1
 * @see HandlerMethodReturnValueHandler
 */
public interface HandlerMethodArgumentResolver {

	/**
	 * Whether the given {@linkplain MethodParameter method parameter} is
	 * supported by this resolver.
	 * @param parameter the method parameter to check
	 * @return {@code true} if this resolver supports the supplied parameter;
	 * {@code false} otherwise
	 */
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * Resolves a method parameter into an argument value from a given request.
	 * A {@link ModelAndViewContainer} provides access to the model for the
	 * request. A {@link WebDataBinderFactory} provides a way to create
	 * a {@link WebDataBinder} instance when needed for data binding and
	 * type conversion purposes.
	 * @param parameter the method parameter to resolve. This parameter must
	 * have previously been passed to {@link #supportsParameter} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @param binderFactory a factory for creating {@link WebDataBinder} instances
	 * @return the resolved argument value, or {@code null}
	 * @throws Exception in case of errors with the preparation of argument values
	 */
	Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

HandlerMethodArgumentResolver 사용 이유

HandlerMethodArgumentResolver를 사용하는 이유는,

매개변수로 사용되는 인자에 대해 공통적으로 처리해야할 로직 등이 있을 경우,

중복 코드를 줄이고 공통 기능으로 추출하여 사용할 수 있다.

동작 방식

Spring에서 Resolver의 동작은 아래와 같은 과정으로 이루어진다.

  1. Client Request 요청

  2. Dispatcher Servlet에서 해당 요청 처리

  3. Client Request에 대한 Handler Mapping
    3.1 RequestMapping에 대한 매칭 (RequestMappingHandlerAdapter가 수행)
    3.2 Interceptor 처리
    3.3 Argument Resolver 처리 <-- Argument Resolver 실행 지점
    3.4 Message Converter 처리

  4. Controller Method invoke

Spring Argument Resovler[jaehun2841.github.io]

정리하자면 특정 RequestHandlerMapping되는 과정에서 invoke 되기 전,

Interceptor > Resolver > MessageConverter 순으로 처리된 후,

Controller의 Method가 invoke 된다.


Resolver 구현

먼저 HandlerMethodArgumentResolverimplemnets하는 Class를 생성한다.

이때 구현해야 하는 method는 supportsParameter, resolveArgument 두 가지이다.


public boolean supportsParameter(MethodParameter parameter)

Parameter가 해당 Resolver에 의해 수행 되는 Type인지 체크하여 boolean을 return한다.

true로 return될 경우 resolveArgument method를 실행한다.

이때 Type 체크를 위해 Class.isAssignableFrom()을 이용해 비교 할 수도,

Annotation을 새로 생성하여 체크할 수도 있다.


.isAssignalbeFrom() 이용 Prameter 객체 Class 타입 체크

아래와 같이 Parameter에서 Binding 되길 원하는 객체의 Class Type을 .isAssignableFrom()을 이용해 구현하면,
isAssignableFrom이 궁금하다면 클릭

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

아래에서 처럼 Handler에서 해당 Parameter 객체 Class 타입을 사용하는 Parameter가 Handelr의 처리 지점이 된다.

	@GetMapping("")
	public ResponseEntity<Contents<Board>> getNotices(ResultJwt resultJwt,
			@RequestParam(required = false) String category, CommonParameter<NoticeType> parameter) {

		String platformId = (String) resultJwt.getClaims().get(JwtClaims.PLATFORMID.getClaim());

Annotation 생성

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

사용된 Annotation들의 속성은 아래와 같다.

  • @interface : 해당 파일을 Annotation Class로 지정, @ResultJWT 라는 Annotation이 생성
  • @Target(ElementType.PARAMETER) : 해당 Annotation이 생성될 위치 지정,
    PARAMETER로 지정 시 Method의 Parameter에서만 사용 가능

  • @Retention(RetentionPolicy.RUNTIME) : Annotation 유지 정책을 설정,
    RUNTIME은 Byte Code File까지 Annotation 정보를 유지,
    reflection을 이용 Runtime시에 해당 Annotation 정보를 획득.
    reflection : 구체적인 Class Type을 알지 못해도, 그 Class의 method, type, field들에 접근할 수 있도록 해주는 Java API

	@GetMapping("")
	public ResponseEntity<Contents<Board>> getNotices(@ResultJWT ResultJwt resultJwt,
			@RequestParam(required = false) String category, CommonParameter<NoticeType> parameter) {

		String platformId = (String) resultJwt.getClaims().get(JwtClaims.PLATFORMID.getClaim());

위와 같이 @ResultJWT Annotation 적용 후 supportsParameter()를 아래와 같이 구현 시,

해당 Parameter가 Resolver의 처리 지점이 된다.

    @Override
    public boolean supportsParameter(MethodParameter parameter) {

        boolean isResultJwtAnnotation =  parameter.getParameterAnnotation(ResultJWT.class) != null;

        boolean isResultJwtClass = ResultJwt.class.equals(parameter.getParameterType());

        return isResultJwtAnnotation && isResultJwtClass;
    }

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception

실제 Parameter와 Binding하여 return할 Object를 생성하는 method이다.

NativeWebRequest Object에 접근하여 Client Request의 ParameterController 보다 우선적으로 받아 작업할 수 있다.

해당 Handler method안 Parameter에서 Binding을 원하는 객체 Type에 맞게 return해주면 된다.

public class ResultJwtArgumentResolver implements HandlerMethodArgumentResolver {
	@Autowired
	private AuthService authService;

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

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
			WebDataBinderFactory binderFactory) throws Exception {
                // return type은 본인이 Binding을 원하는 객체 Class
                // supportsParameter에서 검증한 ResultJwt.class
		return authService.getResultJwt(webRequest.getHeader("Authorization"));
	}
}

두 가지 method를 구현하는 Class를 생성해준 후,

servlet-context.xml에 등록하거나, Configuration을 이용해 Resolver를 등록해주면 된다.

servlet-context.xml 등록

	<mvc:annotation-driven>
		<mvc:argument-resolvers>
			<bean class="project.config.resolver.ResultJwtArgumentResolver"></bean>
		</mvc:argument-resolvers>
	</mvc:annotation-driven>

Configuration 이용

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final ResultJwtArgumentResolver resultJwtArgumentResolver;

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

실제 적용

실제 본인의 경우아래와 같이 @RequestHeaderHeader에서 JWT 토큰을 가져와 Claims를 추출하는 공통 로직 부분

	@GetMapping("")
	public ResponseEntity<Contents<Board>> getNotices(@RequestHeader(value = "Authorization") String jwt,
			@RequestParam(required = false) String category, CommonParameter<NoticeType> parameter) {

		ResultJwt resultJwt = authService.getResultJwt(jwt);
		String platformId = (String) resultJwt.getClaims().get(JwtClaims.PLATFORMID.getClaim());

간단한 Resolver를 구현하여 공통 로직을 추출하였고,

public class ResultJwtArgumentResolver implements HandlerMethodArgumentResolver {
	@Autowired
	private AuthService authService;

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

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
			WebDataBinderFactory binderFactory) throws Exception {
		return authService.getResultJwt(webRequest.getHeader("Authorization"));
	}
}

아래와 같이 효과적으로 바로 Handler에서 매개변수로 사용할 수 있게 적용해 사용 중이다.

	@GetMapping("")
	public ResponseEntity<Contents<Board>> getNotices(ResultJwt resultJwt,
			@RequestParam(required = false) String category, CommonParameter<NoticeType> parameter) {
		String platformId = (String) resultJwt.getClaims().get(JwtClaims.PLATFORMID.getClaim());

좋은 웹페이지 즐겨찾기