프로그래머스 강의 과제를 하던 중 클라이언트에서 보낸 쿼리 파라미터 값이 컨트롤러 파라미터에 바인딩이 되지 않아서 삽질을 했다. Spring 프레임워크에서 지원하는 Pageable 인터페이스를 사용하지 않고, 직접 Pageable 인터페이스를 생성해서 사용했다.
클라이언트에서 쿼리 파라미터로 값 보내기
포스트 목록을 조회하는데, 쿼리 파라미터 값으로 offset 값은 0, limit 값은 5를 보내고 있다.
컨트롤러에서 포스트 목록 조회 메서드
컨트롤러에서는 리스트 조회를 하기 위한 메서드를 다음과 같이 구현했다. 이 때, Pageable 인터페이스는 직접 생성했다.
@RestController
@RequestMapping("api")
public class PostRestController {
@GetMapping(path = "user/{userId}/post/list")
@ApiOperation(value = "포스트 목록 조회")
public ApiResult<List<Post>> posts(
@AuthenticationPrincipal JwtAuthentication authentication,
@PathVariable
Long userId,
Pageable pageable
) {
return new ApiResult<>(postService.findAll(
Id.of(User.class, userId),
authentication.id,
pageable.getOffset(),
pageable.getLimit()));
}
}
클라이언트에서 보낸 요청 값을 이용하지 않고, 기본 생성자에 설정한 기본값으로 인스턴스가 생성되는 문제가 있었다. 왜 요청한 값을 받지 못하는 것일까? 구글 검색을 하던 중 HandlerMethodArgumentResolver 인터페이스를 사용해야 하는 것을 알 수 있었다.
HandlerMethodArgumentResolver
HandlerMethodArgumentResolver 인터페이스는 컨트롤러에서 파라미터를 바인딩 해주는 역할을 수행한다. 특정 클래스 또는 어노테이션에 대해서 파라미터를 처리해야 하는 경우에 사용한다.
HandlerMethodArgumentResolver 인터페이스에는 두 개의 메서드가 선언되어 있다.
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception;
}
- supportsParameter 메서드 : 바인딩 하고자 하는 타겟팅 클래스를 지정한다.
- resolveArgument 메서드 : 요청한 값을 타겟팅 객체에 바인딩 등 조작한다.
PageableMethodArgumentResolver 생성
PageableMethodArgumentResolver 클래스를 다음과 같이 생성한다. supportsParameter에서는 바인딩 하고자 하는 클래스인지 체크하고, resolveArgument 메서드에서는 바인딩 될 클래스의 인스턴스를 생성한다. 지금은 요청으로 넘어온 값을 단순히 타입만 변환해서 사용하고 있지만, 값에 대한 유효성 체크 등 여러 로직이 더 추가되어야 한다.
public class SimplePageableHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Pageable.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) {
String offsetString = webRequest.getParameter("offset");
String limitString = webRequest.getParameter("limit");
int offset = Integer.valueOf(offsetString);
int limit = Integer.valueOf(limitString);
return new SimplePageable(offset, limit);
}
}
Custom ArgumentResolver 등록
HandlerMethodArgumentResolver 인터페이스를 구현한 클래스를 생성했으면, Spring MVC에서 알 수 있도록 등록해주는 작업을 반드시 해야한다.
WebMvcConfigurer 인터페이스를 구현한 클래스에서 addArgumentResolvers 메서드를 오버라이드(재정의) 한다.
HandlerMethodArgumentResolver 인터페이스를 구현한 SimplePageableHandlerMethodArgumentResolver 클래스를 Bean으로 등록하고, resolvers 리스트에 추가한다.
@Configuration
public class WebMvcConfigure implements WebMvcConfigurer {
// 빈 생성
@Bean
public SimplePageableHandlerMethodArgumentResolver simplePageableResolver() {
return new SimplePageableHandlerMethodArgumentResolver();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(simplePageableResolver())
}
}
'Spring' 카테고리의 다른 글
Spring Core 라이브러리 이용해서 Properties 파일 데이터 읽기 (0) | 2019.05.28 |
---|---|
Spring Camp 2019 강의 자료 모음 (0) | 2019.05.06 |
Spring HandlerMethodArgumentResolver 인터페이스 (2) | 2019.05.05 |
Spring Boot 배너 만들기 (0) | 2019.05.04 |
REST API 문서 자동화 도구 Swagger 참고 문서 (0) | 2019.05.04 |
Spring Boot에서 JSP 사용하기 (5) | 2019.04.22 |
굉장히 좋은 경험이네요 ~ 많은 도움을 받았습니다. 한겜하시죠 !
답글
혹시 HandlerMethodArgumentResolver 구현시
몇가지 특정 파라미터만 resolveArgument 메서드 내에서 커스텀 처리 후 바인딩 하고,
나머지 파라미터들은 원래대로 스프링이 기본적으로 바인딩 처리되게 하려면 어떻게 하면 되는지 아시나요?
일일히 request 에서 모든 파라미터를 꺼내서 객체 생성 후 타입변환 및 set 해줘야만 하는 것이 여간 귀찮고 불편해서요. resolveArgument 에서 제외한 필드들에 대해선 스프링이 알아서 나머진 해주겠지 생각하고 제외 시켰더니 아예 null 로 들어가버리네요 ㅠㅠ
---
예를 들어 특별히 커스텀 처리를 하고 싶은 파라미터는 paramA 뿐인 상황입니다.
// resolveArgument 메서드 내
Domain domain = new Domain();
String paramA = webRequest.getParameter( "paramA" );
paramA = retouch(paramA); // 딱 이거하려고 구현한 거
String paramB = webRequest.getParameter( "paramB" ); // 불필요함 ㅠㅠ
String paramC = webRequest.getParameter( "paramC" ); // 불필요함 ㅠㅠ
int paramD = Integer.parseInt(webRequest.getParameter( "paramD" )); //불필요함 ㅠㅠ
(...아래의 10개 필드에도 같은 짓을 해줘야 하는...)
// 단지 paramA 만 특별히 처리하려고 구현한 건데
// 아랫 놈들도 모두 꺼내서 노가다 처리해야 하니... 여간 귀찮습니다
// 그렇다고 하나라도 안하면 꺼낼 때 null ㅠㅠ
// 노가다 처리를 다 set 해주고
domain.setParamA(paramA);
domain.setParamB(paramB);
domain.setParamC(paramC);
domain.setParamD(paramD);
(...아래의 10개 필드에도 같은 짓을 해줘야 하는...)
// 결과
return domain;
---
paramA 에 대해서만 특별하게 처리하고 싶고
나머지는 스프링이 기본 바인딩 처리를 하고 싶은데
이러한 경우는 어떻게 하면 좋을까요?
다른 방법이 있을지요
답글