[Spring] ArgumentResolver 분석하기

임현규·2023년 4월 20일
0

개인 공부

목록 보기
1/11

ArgumentResolver

Spring의 ArgumentResolver는 요청 파라미터를 메소드의 인자로 변환해주는 기능이다.

호출 시점


https://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:ptl:spring_mvc_architecture

Spring의 핵심 컴포넌트이다. 위 그림에서 DispatcherServlet은 Controller 메서드를 요청하기 위해 HandlerAdapter를 사용하는데 메서드 호출 이전에 필요한 인자를 ArgumentResolver를 이용해 생성한다.

ArgumentResolver의 호출 시점은 다음과 같다.
디스패처 서블릿이 handlerMapping을 이용해 Controller에서 정의한 URL를 찾는다. 그 이후 찾은 Controller의 메서드를 호출하기 위해 DispatcherServlet은 HandlerAdapter을 실행한다. 이 때 Controller 메서드에서 정의한 메서드 인자를 확인하게 되는데 해당 매개변수의 어노테이션 또는 타입을 기반으로 체크하고 용도에 맞게 객체를 반환해준다.

이러한 과정을 통해 ArgumentResolver는 Controller 호출 시점 이전에 데이터를 바인딩함으로써 Controller에서 처리해야하는 인자에 대한 부담을 줄여준다.

ArgumentResolver 용도

ArgumentResolver는 컨트롤러 인자에 필요한 객체를 바인딩하는 것으로 여러 용도로 사용된다. spring에서 제공하는 @RequestParam이나 @AuthenticationPrincipal 모두 ArgumentResolver를 활용한다. 이렇게 제공하는 것 외에 요청에 필요한 parameter에 필요한 정보를 HttpRequestServlet, SecurityContext 또는 그 외에 어떤 곳에서든 가져와서 바인딩하고 싶다면 ArgumentResolver를 활용하면 된다.

동작 과정 상세 분석하기

요약

InvocableHandlerMethod에서 등록된 HandlerMethodArgumentResolver가 해당 인자를 지원하는 지 확인 후 controller 메서드 호출에 필요한 인자를 생성한다.

DispatcherServlet

doDispatch 메서드를 살펴보면 HandlerAdapter를 호출하고 mappedHandler에서 handler를 호출해 HandlerAdapter에서 이를 호출함을 알 수 있다. 자세한 부분은 HandlerAdapter에서 설명하겠다.

HandlerAdapter

우선 HandlerAdapter에서 ArgumentResolver를 호출한다고 했으니 HandlerAdater를 먼저 분석해보자

Interface that must be implemented for each handler type to handle a request. This interface is used to allow the DispatcherServlet to be indefinitely extensible. The DispatcherServlet accesses all installed handlers through this interface, meaning that it does not contain code specific to any handler type.
Note that a handler can be of type Object. This is to enable handlers from other frameworks to be integrated with this framework without custom coding, as well as to allow for annotation-driven handler objects that do not obey any specific Java interface.
This interface is not intended for application developers. It is available to handlers who want to develop their own web workflow.

  • 요청을 처리하기 위해 각 처리기 유형에 대해 구현해야 하는 인터페이스다.
  • 이 인터페이스는 DispatcherServlet을 무한정 확장할 수 있도록 하는 데 사용된다.
  • DispatcherServlet은 이 인터페이스를 통해 설치된 모든 핸들러에 액세스한다. 이 - 인터페이스는 애플리케이션 개발자를 위한 것이 아니다.


HandlerAdapter 인터페이스는 2개의 메서드를 지원한다.

하나는 supports 그리고 다른 하나는 handle이다.

supports는 핸들러 인스턴스가 주어지면 이 HandlerAdapter가 지원할 수 있는지 여부를 반환하는 역할을 한다.

핵심은 handle로 HttpServletRequest와 HttpServletResponse 그리고 handle를 인자로 받는다. handle의 역할은 handlerMapping으로 찾은 handlerMethod를 활용해 컨트롤러에 정의된 메서드를 실행하고 ModelAndView 객체를 얻어오는데 중요한 역할을 수행한다.

AbstractMappingHandlerAdapter

RequestMappingHandler의 부모 타입이다. HandlerAdapter를 템플릿 패턴을 이용해 어느정도 구조를 정의해놓은 클래스라 할 수 있다.

여기서 우리가 알아야 할 정보는 handle이다

handle 코드를 살펴보면 handleInternal()를 호출하고 handleInternal()을 abstract와 protected로 정의해서 상속받은 자식 타입에 handle 로직을 위임함을 알 수 있다.

RequestMappingHandlerAdapter

HandlerAdapter의 구현체이자, AbstractMappingHandlerAdapter의 자식 타입이다. 여기서 handleInterval 메서드를 통해 실제 HandlerAdapter가 어떤 방식으로 처리되는지 알 수가 있다

해당 코드를 간단히 설명하면 다음과 같다

1. checkRequest(request) 메서드를 호출하여 요청이 올바른지 검증한다
2. synchronizeOnSession 변수의 값에 따라 요청 처리를 동기화할지 여부를 결정한다  (synchronizeOnSession 변수가 true로 설정되어 있는 경우, 요청 처리를 세션에 대한 동기화 블록으로 감싸고, false로 설정되어 있는 경우, 동기화를 하지 않는다)
3. invokeHandlerMethod(request, response, handlerMethod) 메서드를 호출하여 요청에 해당하는 컨트롤러 메서드를 실행하고, 그 결과를 ModelAndView 객체로 반환한다.
4. 요청 처리가 완료된 후, HTTP 응답 헤더에 캐시 정보를 추가한다. 이 때, 세션 속성을 사용하는 컨트롤러 메서드가 있으면 cacheSecondsForSessionAttributeHandlers 변수에 설정된 값으로 캐시를 설정합니다.
5. ModelAndView 객체를 반환합니다.

여기서 실제 컨트롤러의 메서드를 실행하고 결과를 받아오는 invokeHandlerMethod를 살펴보자

해당 코드를 살펴보면 HandlerAdapter에서 handle시 실제 컨트롤러의 메서드를 호출하기 이전에 ArgumentResolvers를 등록하고 DataBinderFactory를 설정함을 알 수 있다.(WebDataBinderFactory는 요청 매개변수를 객체에 바인딩하는 데 사용되는 WebDataBinder의 인스턴스를 생성하는 역할을 합니다. 즉, 클라이언트로부터 전달된 데이터를 자바 객체로 변환하는 역할을 수행한다. 이를 통해 요청 매개변수의 값을 검증하거나 형식을 변환하여 컨트롤러가 처리할 수 있는 형식으로 매개변수를 바인딩할 수 있게 된다.)

InvocableHandlerMethod

HandlerAdapter는 InvocableHandlerMethod에 필요한 정보를 주입하고 실행해서 ModelAndView를 받아오는 Manager 역할을 담당하는 반면 실제 컨트롤러 메서드 호출과 ArgumentResolver를 이용해 필요한 인자를 바인딩하는 작업은 InvocableHandlerMethod가 담당한다.

invokeAndHandle에서 returnValue를 얻어오는 invokeForRequest를 살펴보면 다음과 같다

여기서 눈에 띄는 로직은 2가지이다. argument 객체를 가져오기 이전에 getMethodArgumentValues메서드를 호출해서 args 객체를 가져오는 것과 doInvoke(args)를 이용해서 실제 메서드를 호출하는 부분 2가지다.

getMethodArgumentValues 메서드를 살펴보면 다음과 같다

Argument를 가져오기 위해서 등록된 resolver가 해당 파라미터를 지원하는 지 확인하고 지원한다면 resolveArgument를 통해 데이터를 바인딩해서 인자를 가져오는 작업을 수행한다.

이 때 resolveArgument 메서드는 HandlerMethodArgumentResolver 인터페이스의 메서드이다.

AbstractNamedValueMethodArgumentResolver

실제로 Spring에서 제공하는 @RequestParam, @PathVarible은 모두 NamedValue 기반으로 동작한다. 이들의 ArgumentResolver는 모두 AbstractNamedValueMethodArgumentResolver 테플릿을 상속하여 구현한다.
그렇기에 만약 ArgumentResolver를 직접 구현해서 추가해야 한다면 해당 템플릿 클래스를 상속하여 구현하면 Spring에 호환성이 좋고 안전하게 구현할 수 있다.

로직은 다음과 같은 순서로 진행한다

  1. NamedValue를 가져온다.
  2. resolveName을 통해 인자에 필요한 데이터를 바인딩한다
  3. 2번 과정에서 가져온 인자 값이 null이라면 NamedValue의 속성에 따라 로직을 처리한다
    • name 속성 관련 처리
    • required 속성 여부에 따라 로직 처리
    • defaultValue 속성 관련 처리

NamedValue는 NamedValue 템플릿의 중첩 클래스로 정의되어 있다.

상당히 익숙한, 많이 본 클래스인데 로직처리를 위해 속성들 값은 저장해 놓는 역할을 수행하는 타입이라 생각하면 된다. 이는 NamedValueMethodArgumentResolver에서만 사용된다.


resolveName은 핵심 로직을 구현하기 위한 메서드이다. abstract로 되어있기 때문에 해당 메서드를 상속해서 핵심 로직을 구현한다

profile
엘 프사이 콩그루

0개의 댓글