Spring - 컨트롤러 메소드 파라미터에 대한 공통 처리 로직을 한 번에! HandlerMethodArgumentResolver 기본 정리
개요
@GetMapping("order")
public ApiResult<List<OrderDto>> findAll(
...
@RequestParam("offset") int offset,
@RequestParam("limit") int limit) {
...
}
가령 Spring MVC로 구현된 API에서 offset과 limit를 활용한 페이징을 지원한다고 한다면 페이징 처리가 필요한 메소드마다 위와 같이 offset과 limit 파라미터를 받는 코드를 작성해야 한다.
위와 같이 @RequestParam을 사용해 중복적으로 파라미터를 받아야 되는 것도 문제이나, offset 혹은 limit에 값의 범위를 지정하여 범위를 벗어나면 기본값을 넣는다거나 하는등 파라미터에 대한 추가적인 처리가 필요하다고 가정해보자.
그렇다면 페이징 파라미터에 대한 추가적인 처리에 대한 코드도 중복적으로 각 필요한 메소드에 적용해야할 것이다.
HandlerMethodArgumentResolver 소개
위와 같은 상황에서 사용할 수 있는, Spring 3.1부터 제공되는 'HandlerMethodArgumentResolver'라는 인터페이스가 있다. Spring 공식문서에는 다음과 같이 설명되어 있다.
Strategy interface for resolving method parameters into argument values in the context of a given request.
컨텍스트에 담겨져 온 요청에서 요청값들을 메소드의 파라미터로 주입해줄 때 파라미터 처리 전략을 정의할 수 있는 인터페이스
- parameter로 받는 값이 여러 개가 존재하고(혹은 객체의 필드들이 여러 개가 존재), 그것을 처리하는 코드들의 중복이 발생할 때
- Controller에 공통으로 입력되는 parameter들을 추가하거나 수정하는 등의 여러 공통적인 작업들을 한 번에 처리하고 싶을 때
위에 부합하는 경우 HandlerMethodArgumentResolver를 사용하여 각자 입맛에 맞게 파라미터를 처리할 수 있다.
HandlerMethodArgumentResolver 사용하기
HandlerMethodArgumentResolver의 사용법은 우선 HandlerMethodArgumentResolver 인터페이스를 Implements한 구현체를 생성해주고, 스프링 컨텍스트에 등록 해주면 설정이 완료된다.
간단히 페이징 파리미터를 처리하는 예제로 알아보도록 하겠다.
1. offset과 limit를 parameter로 받을 객체를 생성한다
public class SimplePageRequest {
private final long offset;
private final int limit;
public SimplePageRequest() {
this(0, 5);
}
public SimplePageRequest(long offset, int limit) {
this.offset = offset;
this.limit = limit;
}
public long getOffset() {
return offset;
}
public int getLimit() {
return size;
}
}
2. HandlerMethodArgumentResolver 구현체 작성
HandlerMethodArgumentResolver 인터페이스를 상속하면 아래 두 가지 메소드를 구현해줘야 한다.
- supportsParameter: 주어지는 파라미터들이 구현된 Resolver에 의해 지원이 되는지 판단하여 boolean 반환 (true면 resolveArgument 메소드에 의해 파라미터 처리 시작, false면 넘어감)
- resolveArgument: 파라미터를 처리하는 메소드로 request에 담겨져온 파라미터들을 용도에 맞게 처리하여 반환
각 메소드와 파라미터에 대한 설명은 Spring 공식문서에 자세히 안내되어 있다!
import org.springframework.core.MethodParameter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class SimplePageRequestHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final String DEFAULT_OFFSET_PARAMETER = "offset";
private static final String DEFAULT_LIMIT_PARAMETER = "limit";
private static final long DEFAULT_OFFSET = 0;
private static final int DEFAULT_LIMIT = 5;
private String offsetParameterName = DEFAULT_OFFSET_PARAMETER;
private String limitParameterName = DEFAULT_LIMIT_PARAMETER;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return SimplePageRequest.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) {
String offsetString = webRequest.getParameter(offsetParameterName);
String limitString = webRequest.getParameter(limitParameterName);
long offset = DEFAULT_OFFSET;
int limit = DEFAULT_LIMIT;
if(StringUtils.hasText(offsetString)) {
offset = Long.valueOf(offsetString);
if(offset < 0 || offset > Long.MAX_VALUE) {
offset = DEFAULT_OFFSET;
}
}
if(StringUtils.hasText(limitString)) {
limit = Integer.valueOf(limitString);
if(limit < 1 || limit > 5) {
limit = DEFAULT_LIMIT;
}
}
return new SimplePageRequest(offset, limit);
}
}
위와 같이 파라미터로 전달된 offset과 limit를 입맛에 맞게 처리할 수 있다!
3. addArgumentResolvers를 통해 HandlerMethodArgumentResolver 구현체 등록
WebMvcConfigurer의 addArgumentResolvers 메소드를 오버라이딩하여 구현된 Cusmtom MethodArgumentResolver를 아래와 같이 등록해줄 수 있다.
@Configuration
public class WebMvcConfigure implements WebMvcConfigurer {
@Bean
public SimplePageRequestHandlerMethodArgumentResolver simplePageRequestHandlerMethodArgumentResolver() {
return new SimplePageRequestHandlerMethodArgumentResolver();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(simplePageRequestHandlerMethodArgumentResolver());
}
}
그렇다면 이제 아래와 같이 페이징 파라미터를 받을 수가 있고, 커스텀 Resolver에 정의한대로 범위 벗어난 값이 들어오면 자동으로 기본값이 설정되어 pageRequest에 전달될 것이다!
@GetMapping("order")
public ApiResult<List<OrderDto>> findAll(
...
SimplePageRequest pageRequest) {
...
}
요청으로부터 날아오는 파라미터에 대한 처리에 대해서 컨트롤러에서 신경쓰지 않아도 되고 중복 코드도 발생하지 않기 때문에 상당히 편리하게 활용할 수 있다.
마무리
HandlerMethodArgumentResolver는 스프링 컨텍스트가 요청과 매핑된 메소드에 인자를 전달할 때, 인자에 대한 처리를 정의할 수 있다. 이는 인자에 대한 공통적인 처리 로직이 필요할 때 코드 중복을 방지할 수 있다는 장점이 있다.
여담으로 이번 글에서 페이징 파라미터인 offset과 limit으로 예시를 들었지만, Spring은 기본적으로 아래의 클래스를 통해 페이징 파라미터(page, size, sort)를 메소드 파라미터로 받아 사용할 수 있도록 지원하고 있다.
org.springframework.boot.autoconfigure.data.web.Pageable
Reference
Spring Docs - HandlerMethodArgumentResolver