사용자가 컨트롤러의 메서드 인자값으로 임의의 값을 전달하려할 때 사용된다.
Argument Resolver를 Controller 단에서 사용하면 중복 코드(HttpSession에서 세션 로드, HttpServletRequest에서 요청 url 및 ip 정보 로드 등)를 깔끔하게 처리할 수 있다.
RequestMapping에 대한 매칭 (RequestMappingHandlerAdapter가 수행) 이후
Interceptor 처리되고 그 후에
Argument Resolver가 파라메터를 처리한다.
언제 사용하면 좋을까?
내가 생각한 몇가지 예시는 다음과 같다.
1. 프로덕션 코드에서 코드 구조를 드러내기 싫을 때
Ex) 암호화 -> 복호화 하는 숨기고싶은 구조가 있을 때
2. 파라메터로 받는 인자를 다르게 받고 싶을 때
Ex) 유저 토큰을 받는데 토큰 값을 분해해서 하기 보다는 토큰 안에 있는 값을 처음부터 받아서 처리하고 싶을 때
쓰면 왜 좋을까?
위에서 말한 예시들의 장점이다.
컨트롤러에서 파라미터 가공추가수정할 수 있지만 이방식은 중복코드를 양산한다. 이를 줄이고자 할 때 사용하면 좋을 것 같다.
내부 작동
Spring은 요청을 컨트롤러의 메서드를 wrapping한 InvocableHandlerMethod의 invokeForRequest 메서드로 처리한다.
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
getMethodArgumentValues를 보면
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
...
for (int i = 0; i < parameters.length; i++) {
...
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
}
여기서는 supportsParameter를 통해 이 타입,파라미터가 처리되어질 수 있는지 확인한다.
그리고 밑에 try문을 보면 알 수 있듯이 여러개의 파라미터가 있다면 각각의 파라미터에 대해서 하나하나씩 resolveArgument를 통해 인자를 처리해서 저장한다.
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
위의 반복문에서 argumentResolver를 순회하면서 찾는데 이는 어디에 등록되있는걸까 ?
requestMappingHandler Adapter이다. 여기서 getDefaultInitBinderArgumentResolvers를 통해 HandlerMethodArgumentResolverComposite().addResolvers(resolvers);를 사용하여 값을 넣어주는 것을 확인할 수 있다.
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(20);
// 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 ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
return resolvers;
}
엄청나게 많은 argument resolver들이 있는데 이중에 type에 맞는 리솔버를 사용한다.!
그중에
PathVariableMapMethodArgumentResolver
만 한번 보려고 한다.
@Override
public boolean supportsParameter(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(ann.value()));
}
Pathvariable인지 확인하여 처리 할 수 있는지 확인해 주는 함수이다 !
그리고 이런 방식을 통해 처리가 된다! 여기서 처리되는 것만 알고 있다 ..
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
@SuppressWarnings("unchecked")
Map<String, String> uriTemplateVars =
(Map<String, String>) webRequest.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (!CollectionUtils.isEmpty(uriTemplateVars)) {
return new LinkedHashMap<>(uriTemplateVars);
}
else {
return Collections.emptyMap();
}
}
잘 뜯어봤는지 모르겠다.. 생각보다 안에 까지 보는게 재밌다
'코코코딩공부 > Spring' 카테고리의 다른 글
Repository 사용기 (0) | 2023.06.07 |
---|---|
도메인은 id 값을 가져도 될까 ? (0) | 2023.05.28 |
[Spring] Dispatcher Servlet이란 ? & 미션 에서 찾아보기 (0) | 2023.04.30 |
[Spring] Servlet 이란 ? (0) | 2023.04.30 |
[Spring] 어떤 객체를 빈으로 등록해야 할까 ? (1) | 2023.04.23 |