아래는 Spring 에서 요청 데이터를 받아 원하는 객체를 반환하는 역할을 수행하는 예시이다.
@GetMapping("/check")
public MemberResponse findUser(HttpServletRequest request) {
TokenResponse tokenResponse = authService.extractTokenByCookies(request);
String email = authService.extractEmailByToken(tokenResponse);
return loginMemberService.findNameByEmail(email);
By default, Spring only knows how to convert simple types. In other words, once we submit data to controller Int, String or Boolean type of data, it will be bound to appropriate Java types automatically.
But in real-world projects, that won’t be enough, as we might need to bind more complex types of objects.
위의 이유 때문에 우리는 Member 객체를 바인딩 하기 위해서 해당 작업이 필요한 API 마다 같은 작업을 수행해야 한다.
이는 컨트롤러에 중복 코드가 늘어날 뿐만 아니라, 컨트롤러의 책임이 더 늘어나게 된다는 단점이 있다.
이것을 해결할 수 있는 스프링의 데이터 바인딩 메커니즘이 있다.
Spring 에서 HandlerMethodArgumentResolver 는 요청 데이터를 메서드의 매개변수로 변환할 때 사용하는 전략 인터페이스이다.
다시 말해서, 어떠한 요청이 컨트롤러에 들어왔을 때 요청 데이터로부터 원하는 객체를 반환하는 역할을 수행한다.
Strategy interface for resolving method parameters into argument values in the context of a given request.
아래는 HandlerMethodArgumentResolver 를 implement 받아 custom MemberArgumentResolver 를 구현한 예시이다.
MemberRequest 를 반환하는 역할을 resolveArgument() 가 수행하고 있다.
(참고) 컨트롤러가 도메인에 접근하지 못하도록 Member (domain) 대신 MemberRequest (DTO) 를 사용했다.
@Component
public class MemberArgumentResolver implements HandlerMethodArgumentResolver {
private final AuthService authService;
public MemberArgumentResolver(AuthService authService) {
this.authService = authService;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(MemberRequest.class);
}
@Override
public MemberRequest resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Cookie[] cookies = request.getCookies();
return authService.extractMemberByCookies(cookies);
}
}
HandlerMethodArgumentResolver‘s resolveArgument() method returns an Object. In other words, we could return any object, not only String.
또한 HandlerMethodArgumentResolver 의 function 을 @Override
하여 구현한 것을 알 수 있다.
공식문서를 살펴보면,
HandlerMethodArgumentResolver 의 Function 은 총 2개이다.
주어진 메서드 매개변수가 이 리졸버에서 지원되는지 여부를 반환한다.
Whether the given MethodParameter is supported by this resolver.
boolean supportsParameter(MethodParameter parameter)
주어진 요청 데이터로부터 메서드 매개변수를 해석한다.
Resolves a method parameter into an argument value from a given request. A ModelAndViewContainer provides access to the model for the request. A WebDataBinderFactory provides a way to create a WebDataBinder instance when needed for data binding and type conversion purposes.
@Nullable
Object resolveArgument(MethodParameter parameter,
@Nullable
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable
WebDataBinderFactory binderFactory)
throws Exception
parameter - the method parameter to resolve. This parameter must have previously been passed to supportsParameter(org.springframework.core.MethodParameter) which must have returned true.
📌 설명 : 확인할 메서드 매개변수 - supportsParameter 에서 true 를 반환했어야 함
mavContainer - the ModelAndViewContainer for the current request
📌 설명 : 현재 요청에 대한 ModelAndViewContainer - ModelAndViewContainer는 모델에 대한 액세스와 데이터 바인딩 및 type conversion 을 위해 필요할 때 WebDataBinder 인스턴스를 생성하는 방법을 제공함
webRequest - the current request
binderFactory - a factory for creating WebDataBinder instances
📌 설명 : WebDataBinder 인스턴스를 생성하기 위한 팩토리
이제 MemberArgumentResolver 구현을 마쳤다!
그런데 구현만 해놓는다고 해서 스프링이 자동으로 리졸버를 사용하는게 아니다.
스프링이 search 할 위치를 알려주기 위해서 WebMvcConfigurer 를 이용하면 된다.
아래는 WebMvcConfigurer 를 implement 받아 WebMvcConfig 를 구현한 예시이다.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final MemberArgumentResolver memberArgumentResolver;
public WebMvcConfig(MemberArgumentResolver memberArgumentResolver) {
this.memberArgumentResolver = memberArgumentResolver;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(memberArgumentResolver);
}
}
현재 MemberArgumentResolver 가 @Component
로 빈으로 관리되고 있기 때문에 새로운 객체를 생성하는 대신 생성자를 통해 주입받아 사용해주었다.
이제 다 되었다!
컨트롤러에서 사용이 가능하게 되었다.
아까 살펴본 로그인 check API 에서 어떻게 달라질까?
@GetMapping("/check")
public MemberNameResponse findUser(HttpServletRequest request) {
TokenResponse tokenResponse = authService.extractTokenByCookies(request);
String email = authService.extractEmailByToken(tokenResponse);
return loginMemberService.findNameByEmail(email);
@GetMapping("/check")
public MemberNameResponse findUser(MemberRequest memberRequest) {
return new MemberNameResponse(memberRequest.getName());
}
로그인을 확인하는 API 에서 resolver 를 통해 검증이 완료된 MemberRequest 객체를 받아와 로직이 더 깔끔해졌다 :)
A Custom Data Binder in Spring MVC
Interface HandlerMethodArgumentResolver
HandlerMethodArgumentResolver
MemberArgumentResolver는 MemberRequest를 반환하는데 바뀐 코드에선 Member를 받네요