[Spring] Argument Resolver를 이용한 유연성 있는 파라미터 처리

서비스를 운영하다보면 다양한 종류의 데이터를 받게 됩니다. 그럴 때마다 Controller 부분에서 이를 전처리하게 되는데, 이렇게 되면 각 Controller에 전처리 해야 하는 코드를 함수화 하거나 Utils 클래스를 만들고, 이를 의존성 주입해서 사용하는 방법이 그나마 코드를 줄일 수 있는 방법입니다.

 

그러나 Utils를 사용해도 매번 함수를 호출해야 하는 불편함이 있습니다. 그렇게 되면 코드가 중복되고, 이것이 커지면 역시 코드가 난잡해지게 됩니다. 

 

Spring에서는 이러한 파라미터를 공통으로 처리할 수 있도록 구현된 인터페이스가 있는데, 그것이 바로 Argument Resolver입니다. 

 

 

 

Spring Argument Resolver

API 엔드포인트로부터 들어온 데이터(파라미터)를 가공하여 필요한 데이터만 뽑는 등의 로직이 필요할 경우에 사용하는 Spring Argument Resolver는 HandlerMethodArgumentResolver를 상속하여 애플리케이션에 맞는 새로운 Resolver를 만들고, 애플리케이션 실행시, Resolver 리스트에 추가함으로써 적용할 수 있습니다.

 

그렇다면 이 Resolver는 어떻게 동작하는 것일까요?

 

Spring MVC는 다음과 같은 흐름으로 요청 처리가 이루어집니다.

 

  • 사용자가 웹 브라우저를 통해 요청하면 DispatcherServlet이 이를 받음
  • DispatcherServlet은 해당 요청에 맞는 URI를 HandlerMapping에서 검색

    • 이 때, RequestMapping으로 구현한 API를 찾게 되는데, 이들은 RequestMappingHandlerAdapter가 모두 가지고 있음.
    • 원하는 Mapping을 찾은 경우, 첫 번째로 Intercepter를 처리
    • Argument Resolver 처리
    • Message Converter 처리
  • Controller Method Invoke

보통 저는 Filter와 Intercepter를 API 요청 중간에 많이 사용하는 편입니다. 특히 권한을 요구하거나, 인증이 필요한 구간에서는 Filter를 사용하는데, 이 때 Filter는 DispatcherServlet 요청이 있기 전에 호출되고 (Servlet의 라이프 사이클 참고) Intercepter의 경우 클라이언트가 요청한 API를 찾은 뒤에 호출이 되기 때문에 각 구현체들이 어떻게 호출되는지 알고 사용하는 것이 중요합니다.

 

우리가 사용하고자 하는 Argument Resolver는 Intercepter 요청 뒤에 이루어집니다. 사실 이 부분을 확인해보면, 굳이 Argument Resolver를 사용하지 않아도 Intercepter를 별도로 구현해 처리해도 큰 문제가 없지만 애플리케이션에서 다양한 요구 사항이 존재한다면 프레임워크에서 제공하는 Resolver를 사용했을 때 각 로직을 원하는 구간 별로 나눌 수 있다는 것은 메리트가 있을지도 모르겠다는 생각이드네요.

 

 

 

 

How to use

그렇다면 실제 Spring Framework에서 어떻게 사용할 수 있는지 알아보겠습니다. 그 예시로 어떤 문자열의 값을 주어도 클라이언트의 브라우저 정보를 화면에 띄우는 API를 만들어보도록 하겠습니다.

 

먼저 파라미터 구분을 위한 어노테이션을 한 개 만들어줍니다. 이 어노테이션은 API 엔드포인트에서 특정 데이터임을 가리키기 위한 용도로 사용됩니다.

 

API는 클라이언트가 주는 문자열의 정보를 그대로 반환하도록 합니다.

 

이제 특정 아규먼트 타입에 대한 Resolver를 생성해줍니다. 우리는 이를 위해 위에서 어노테이션을 만들었으므로 HandlerMethodArgumentResolver를 상속받은 후 supportParameter 콜백 함수에서 우리가 만든 어노테이션인지를 반환하도록 함수를 구현하면 됩니다.

 

마지막으로 resolveArgument 함수에서 해당 파라미터가 어떻게 반환되는지를 구현해주면 끝입니다. 우리는 어떤 데이터 값이 들어와도 브라우저 정보만을 주면 되기 때문에 Request 파라미터에서 getHeader를 이용해 헤더 값을 반환하도록 합니다.

 

이제 애플리케이션에 위에서 정의한 Resolver를 추가해야 합니다. 이를 위해서 애플리케이션 메인 클래스에 WebMvcConfigurationSupport 클래스를 상속하고, addArgumentResolver 함수를 재정의하여 해당 파라미터에 있는 argumentResolver 리스트에 위에서 만든 Resolver를 추가해주면 됩니다.

 

 

 

 

Test

이제 브라우저에서 API를 호출하면 위와 같이 브라우저의 정보가 나타남을 알 수 있습니다.

 

 

 

 

 

마치며...

글쓰기 전부터 어떤 예시를 활용할까를 많이 고민해봤지만 쉽게 데이터 전처리 해볼만한 예제를 찾지 못한 게 아쉬웠던 포스트였던 것 같습니다. 

 

실제로 ArgumentResolver를 사용하면 클라이언트로부터 파라미터를 받을 때, 당초 필요한 데이터만을 서버에 주는 것이 올바른 방법입니다만 그렇지 못하는 경우가 한 가지가 있는데, 그 부분 중에 하나는 바로 암호화 된 데이터입니다.

 

만약, 클라이언트에서 암호화된 데이터를 가지고 있다면, 먼저 이를 복호화 해야할 것입니다. 그러나 View 단에서 이를 복호화 한다면 브라우저에 노출될 가능성이 크고, 그렇게 되면 서버에 취약점이 생기게 됩니다. 그렇다면 이를 복호화하는 과정은 서버에서 하는 것이 좋겠죠.

 

만약 각 API마다 이런 작업을 처리해야 한다면, 본래 Controller마다 복호화 처리 코드를 넣어야 하지만 이렇게 되면 코드가 심각하게 지저분해지고, 중복 코드가 많아지면서 코드의 가독성이 자연스레 떨어지게 됩니다. 따라서 이러한 처리를 유연하기 위해 Argument Resolver가 존재하며 이를 잘 활용하는 것이 좋겠습니다.

 

comments powered by Disqus

Tistory Comments 0