HandlerMethodArgumentResolver
HandlerMethodArgumentResolver란?
컨트롤러 메서드에서 특정 조건에 맞는 파라미터가 있을 때 원하는 값을 바인딩해주는 인터페이스이다. 스프링에서의 예를 들자면, Controller에서 @RequestBody
어노테이션을 사용해 요청을 받아올 때, @PathVariable
어노테이션을 사용해 Path variable을 받아올 때 HandlerMethodArgumentResolver를 사용해 값을 받아온다.
HandlerMethodArgumentResolver를 커스텀해서 사용하려면?
컨트롤러에서 특정한 객체가 파라미터로 존재 시, 원하는 값을 바인딩하는 커스텀 HandlerMethodArgumentResolver를 등록해보자.
- 어노테이션을 만든다.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface CustomResolver { }
- HandlerMethodArgumentResolver를 구현한 CustomArgumentResolver를 구현한다.
HandlerMethodArgumentResolver를 구현한 객체는 다음 두 메서드를 구현해주어야한다.
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory);
- supportsParameter
지정된 메서드 매개변수가 해당 resolver에서 지원되는지 여부를 판단한 boolean을 리턴한다. - resolveArgument
실제로 바인딩할 객체를 리턴한다.
public class CustomArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Person.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Person person = new Person();
person.setName("joanne");
return person;
}
}
- Controller 메서드를 작성한다.
@RestController
public class PersonController {
@GetMapping("/")
public Person bindPerson(@CustomResolver Person person) {
return person;
}
}
- Configuration에 Resolver를 등록한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private CustomArgumentResolver customArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(customArgumentResolver);
}
}
HandlerMethodArgumentResolver는 어떻게 동작하는걸까?
Spring은 요청을 컨트롤러의 메서드를 wrapping한 InvocableHandlerMethod
의 invokeForRequest
메서드로 처리한다. 이 때 invokeForRequest
에서는 getMethodArgumentValues
라는 메서드로 args를 가져오는데, 이 때 HandlerMethodArgumentResolverComposite
타입의 객체로 이루어진 resolver들을 가져오고, 이 HandlerMethodArgumentResolverComposite
는 HandlerMethodArgumentResolver
를 상속받은 객체로서 HandlerMethodArgumentResolver
를 상속받은 객체를 가지고 루프를 돌며 지원하는 파라미터인지 확인하고 결과를 리턴한다.
HandlerInterceptor
HandlerInterceptor란?
HandlerInterceptor란 특정한 URI 호출을 가로채는 역할을 한다. 이를 이용하여 컨트롤러의 로직을 수정하지 않고 사전 또는 사후 제어가 가능하다.
HandlerInterceptor 내부 메서드
- preHandle(request, response, handler)
지정된 컨트롤러의 동작 이전에 수행할 동작을 사전 제어한다. - postHandle(request, response, handler, modelAndView)
지정된 컨트롤러의 동작 이후에 처리할 동작을 사후 제어한다. - afterCompletion(request, response, handler, exception)
Dispatcher Servlet의 화면 처리가 완료된 이후 처리할 동작을 다룬다.
HandlerInterceptor 사용하기
로그인과 관련된 기능 중 토큰의 유효성 검사만을 담당하는 인터셉터를 만들어보자.
public class LoginInterceptor implements HandlerInterceptor {
private final AuthService authService;
public LoginInterceptor(AuthService authService) {
this.authService = authService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String accessToken = AuthorizationExtractor.extract(request);
authService.validateToken(accessToken);
return true;
}
}
@Configuration
어노테이션이 붙은 Configurer 클래스에 addInterceptor를 하여 빈으로 등록한다.
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor(authService))
.addPathPatterns(<--INTERCEPTOR를 사용할 URI-->);
}
어디까지 Interceptor를 사용하고, 어디까지 Resolver를 사용할 것인가?
Interceptor는 Request 요청이 왔을 때, 요청 전/후/끝났을 때 해야할 것들을 처리할 때 사용한다. (공통처리)
Resolver는 컨트롤러의 메소드 파라미터를 커스터마이징할 때 사용한다. 즉 원하는 클래스로 바인딩할 때 사용한다!