Spring

Spring ArgumentResolver

voider 2022. 5. 12. 23:53

클라이언트와 통신하다보면 전처리가 필요할 때가 있다.

간단한 예로 session에서 userId를 가져와야 한다고 가정해보겠다.

  1. HttpSession에서 UserID를 받아온다.
  2. UserID를 UserAuth로 변환해서 서비스 레이어로 넘긴다.
@PostMapping("/api/sign-up")
public ResponseEntity<Void> signUp(@RequestBody SignUpReq signUpReq, HttpSession session) {
	Long userId = (Long) session.getAttributre("userId");
	loginService.signUp(signUpReq, AuthUser.of(userId));
  return ResponseEntity.ok().build();
}

Controller에서 HttpSession을 직접 받는 것도 조금 찝찝할 수 있고, 세션 값을 받아와서 변환해야 하는 번거로움이 있다. 그런 것도 그런 거지만 내 생각에 가장 큰 문제는 User 정보 같은 건 자주 필요하기 때문에 이걸 매번 해줘야 할 수도 있다.

애초에 userID를 UserAuth로 변환한 것을 받으면 좋지 않을까?

@PostMapping("/api/sign-up")
public ResponseEntity<Void> signUp(@RequestBody SignUpReq signUpReq, 
									**AuthUser auth**) {
	Long userId = (Long) session.getAttributre("userId");
	loginService.signUp(signUpReq, **auth**);
  return ResponseEntity.ok().build();
}

이렇게 여러 곳에서 똑같이 전처리를 해야 하는 과정을 자동화할 수 있는데 이게 Argument Resolver의 역할이다.

Spring Argument Resolver는 HandlerMethodArugmentResolver라는 인터페이스를 상속하여 애플리케이션에 맞는 새로운 리졸버를 만들고, 실행 시 리졸버를 리스트에 추가함으로써 적용할 수 있다. 이 리졸버를 추가하는 것은 직접 등록을 해줘야 한다.

MVC 흐름

  • 클라이언트의 요청을 DispatcherServlet이 받음
  • DispaterServlet은 HandlerMapping에서 URL을 검색
    • RequestMappingHandlerAdapter가 모든 핸들러 리스트를 가지고 있음.
    • 원하는 URL을 찾은 경우 첫 번째로 인터셉터를 처리한다.
    • Argument Resolver 처리
    • Message Converter 처리
  • Controller Method invoke

구현

public class AuthUserResolver implements HandlerMethodArgumentResolver {
    /**
     * 파라미터가 AuthUser.class라면 resolveArgument 실행한다.
     * @param parameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return AuthUser.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        final Long userId = (Long) webRequest.getAttribute(LOGIN_SESSION_KEY, WebRequest.SCOPE_SESSION);
        if (userId == null)
            throw new RuntimeException("bad request. no session.");
        return AuthUser.of(userId);
    }

HandlerMethodArgumentResolver를 구현해서 아규먼트리졸버를 만들 수 있다.

suppoertsParameter()에서 파라미터를 검증한다. 우리가 지정한 클래스라면 resolverArgument()를 실행한다.

resolverArgument에서 우리가 원하는 전처리를 진행하면 된다.

여기서 끝이 아니고 직접 ArgumentResolver를 Spring에 등록해줘야 한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new AuthUserResolver());
    }
}

WebMvcConfigurer를 상속받아서 addArgumentResolvers()를 오버라이딩 한다.

기존 리졸버들에 우리가 커스텀한 리졸버를 추가해주면 끝.

참고로 예제는 클래스지만 특정 어노테이션이 달린 파라미터를 전처리하는 것도 가능하다.