본문 바로가기

TIL

ArgumentResolver

 

Visual Summary - 학습 이미지

ArgumentResolver 가 무엇인가

우선 코드를 한번 보니 HandlerMethodArgumentResolver 라는 인터페이스를 상속받고있고,

그안에 supportsParameter , resolveArgument를 구현하고있다.

 

알게된 사실 

  1. spring framework 에서 제공하는 인터페이스이다.
  2. HTTP 요청 즉 request 안에있는 데이터들을 꺼내서 컨트롤러에 정의한 파라미터 값으로 만들어 주는 역할그래서 종류별로 RequestParamResolver , PathVariableResolver , RequestBodyResolver 이런 것들이있고 HandlerMethodArgumentResolver 를 상속 받아서 커스텀 하게 내가 원하는 Resolver를 만들 수 있는 것이다.
    즉 /test?name=jaemin → 이렇게 요청이 들어왔을때 우리 컨트롤러는 String name 이렇게 받아야하는데 이때 “jaemin" → String name 으로 변환해 주는게 resolver이다
  3. supportsParameter 는 parameter가 해당 resolver를 지원하는 여부 확인하는 메서드이다 이게 무슨말인가..? 이해가 안갔는데 코드로 보니 확실하게 이해 할 수 있다, PathVariableResolver 안에 이런식으로 구현이 되어있다고 보면  
@Override
public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(PathVariable.class);
}

@PathVariable, @RequestBody 등 어노테이션이 붙어있는데 지금 파라미터가 @PathVariable가 붙어있어? 라고 물어보는 것이다.

그래서 spring 의 동작을 보면 아래와 같은 resovler들이 있을때 해당 parameter에 어떤 어노테이션이 붙어있는지 검증 하는것이다.

PathVariableResolver
RequestParamResolver
RequestBodyResolver
AuthenticationPrincipalResolver
CustomResolver

PathVariableResolver.supportsParameter(parameter)
RequestParamResolver.supportsParameter(parameter)
RequestBodyResolver.supportsParameter(parameter)
...

   4. resolveArgument 는 지원을 한다면 parameter를 argument value로 변환, 바인딩 하는 역할 즉 resolver의 역할인 데이터를 변환해준다.

 

동작시점에 관하여

  • Argument Resolver는 Interceptor 가 처리된 후 처리된다.
  1. Client 가 요청한다(Request).
  2. Dispatcher Servlet에서 Request를 처리한다.
  3. 요청을 분석하여 Request에 대한 Hadler Mapping을 한다.
    • RequestMappingHandlerAdapter(핸들러 매핑에 맞는 어댑터 결정).
    • Interceptor 처리
    • Argument Resolver 처리 (등록한 리졸버에 대응되는 파라미터 바인딩)
    • Message Converter 처리
  4. Controller Method invoke

문제 (config 등록)

boolean hasAuthAnnotation = parameter.getParameterAnnotation(Auth.class) != null;
boolean isAuthUserType = parameter.getParameterType().equals(AuthUser.class);

// @Auth 어노테이션과 AuthUser 타입이 함께 사용되지 않은 경우 예외 발생
if (hasAuthAnnotation != isAuthUserType) {
    throw new AuthException("@Auth와 AuthUser 타입은 함께 사용되어야 합니다.");
}

구현된 resolver 안에서 @Auth 어노테이션과 AuthUser를 같이쓰게 되어있는데

Cannot invoke "java.lang.Long.longValue()" because the return value of "org.example.expert.domain.common.dto.AuthUser.getId()" is null

라는 에러가 발생하는데 처음엔 왜 null 에러가 발생하지? 했는데 resolver가 무엇인지 알고나니까

해당 어노테이션이 정상적으로 동작하지 않아서 (@Auth 가 어떤 resolver에도 걸리지 않아서) 데이터 바인딩을 해주지 못하니 그 안에 .getId()를 해도 당연히 아무런 데이터를 가져올 수 없는것이다.

 

해결

컨트톨러에서 정상적으로 사용해도 resovler에서 체크 되지 않았던 이유는 구현한 resolver 는 WebMvcConfigurer 에 addArgumentResolver() 메소드로 구현한 resolver 추가 해주어야 하는데 해주지 않았다.

위 사진 처럼 등록해주면 정상적으로 동작하는 것을 확인 할 수 있다.