Programing/Framework

[Spring MVC] HandlerMethodArgumentResolver 구현하기

나모찾기 2020. 9. 18. 14:32

가끔 Controller 에 특정 타입의 값을 자동으로 받고 싶을 경우가 있다.

path가 "foo/{a}/{b}"  인데 a 랑 b값을 aggregation 해서 특정 객체로 만들고 싶은데 프레임워크에게 시키고 싶은 것이다.

 

이럴 경우 @PathVariable 에 대한 메서드 인자 Resolver 를 만들어주면 해결이 가능하다.

 

기본적으로 @PathVariable Resolver는 두 가지가 있다.

PathVariableMethodArgumentResolver
PathVariableMapMethodArgumentResolver

위의 경우는 파라미터의 이름이나 @PathVariable 에 지정된 value 의 값을 매핑시켜준다.

가끔 타입 변환이 필요한 경우에는 conversionService에게 타입 변환을 요청한다.

 

PathVariableMapMethodArgumentResolver 의 경우 Map의 타입으로 변환을 한다.

스프링이 기본적으로 추가해주는 ArgumentResolver 목록

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
	private List<HandlerMethodArgumentResolver> customArgumentResolvers;
	// ..
	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}

	public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
		return this.customArgumentResolvers;
	}

위의 목록들의 계층구조은 아래와 같다.

하지만 눈으로 볼수  없다!

Resolver 관련 annotaion 용도
HandlerMethodArgumentResolver   <인터페이스>
AbstractNamedValueMethodArgumentResolver   <추상 클래스>
- ServletCookieValueMethodArgumentResolver   쿠키 값을 받을 수 있게 해줌
- SessionAttributeMethodArgumentResolver @SessionAttribute NativeWebRequest의 RequestAttributes.SCOPE_SESSION 속성 값을 획득
- ExpressionValueMethodArgumentResolver @Value  
- MatrixVariableMethodArgumentResolver @MatrixVariable  
- RequestAttributeMethodArgumentResolver @RequestAttribute  
- RequestHeaderMethodArgumentResolver @RequestHeader  
- RequestParamMethodArgumentResolver @RequestParam, @RequestPart  
PathVariableMethodArgumentResolver @PathVariable  
ErrorsMethodArgumentResolver Errors.class  
HttpEntityMethodProcessor HttpEntity.class || RequestEntity.class  
MapMethodProcessor Map.class  
MatrixVariableMapMethodArgumentResolver @MatrixVariable  
ModelMethodProcessor Model.class  
PathVariableMapMethodArgumentResolver @PathVariable  
RedirectAttributesMethodArgumentResolver RedirectAttributes.class  
RequestHeaderMapMethodArgumentResolver @RequestHeader + Map.class  
RequestParamMapMethodArgumentResolver @RequestParam  
RequestParamMethodArgumentResolver @RequestParam  
RequestPartMethodArgumentResolver @RequestPart, @RequestParam  
RequestResponseBodyMethodProcessor @RequestBody  
ModelAttributeMethodProcessor @ModelAttribute, @ConstructorProperties 임의의 객체(Model)을 Contoller의 메서드에 사용시
ServletModelAttributeMethodProcessor   ModelAttributeMethodProcessor 의 서블릿 한정 사용되는 처리기
URI 템플릿 변수의 모델 속성을 초기화하는 fall-back 전략으로도 사용한다.
ServletRequestMethodArgumentResolver WebRequest.class || ServletRequest.class || MultipartRequest.class || HttpSession.class || Principal.class || InputStream.class || Reader.class || HttpMethod.class || Locale.class || TimeZone.class ||
java.time.ZoneId
HttpServletRequest ...
ServletResponseMethodArgumentResolver ServletResponse.class || OutputStream.class || Writer.class ServletResponse ...
SessionStatusMethodArgumentResolver SessionStatus.class  
UriComponentsBuilderMethodArgumentResolver UriComponentsBuilder.class or ServletUriComponentsBuilder.class  

annotation 확인을 위해서는 parameter.hasParameterAnnotation(annotation.class) 으로

class 타입의 확인은 parameter.getParameterType() (Class<?> type) 으로

타입의 이름은 paramType.getName()  를 통해 하고 있다.