🛠️ 실습 환경
Lang: Java 17
Framework: Spring Boot 3.1.5📝 실습 목표
쿼리 파라미터의 개수가 많아질 때 하나의 불변 객체로 바인딩 하기
GET
요청에 대해 다음과 같은 코드를 작성하게 됩니다.@GetMapping("/uri")
public ...리턴값 getFoo() {
...
return ... 리턴값
}
@GetMapping("/uri")
public ...리턴값 getFoo(
@RequestParam(name = "val1") String val1,
@RequestParam(name = "val2") String val2
) {
...
return ... 리턴값
}
@RequestParam
애노테이션과 매개변수로 코드 가독성이 저해될겁니다.https://domain.com/resource?val1={val1}&val2={val2}&val3={val3}&val4={val4}...
@GetMapping("/uri")
public ...리턴값 getFoo(
@RequestParam(name = "val1") String val1,
@RequestParam(name = "val2") String val2,
@RequestParam(name = "val3") String val3,
@RequestParam(name = "val4") String val4,
...
) {
...
return ... 리턴값
}
GET
요청에도 본문을 포함 할 수 있다고 합니다.GET
요청 파라미터에도 @RequestBody
애노테이션을 붙여 요청 본문을 받을 수 있습@GetMapping("/uri")
public ...리턴값 getFoo(
@RequestBody Foo foo
...
) {
...
return ... 리턴값
}
Foo
객체는 쿼리 파라미터로 받아온 파라미터를 바인딩한게 아니라 말 그대로 요청 본문의 내용(json or sth...)을 Foo
타입으로 역직렬화 한 것입니다.GET
요청에 필요한 내용을 본문에 담는건 상황에 따라 적합하지 않을 수 있습니다.@GetMapping("/uri")
public ...리턴값 getFoo(
@RequestBody Map<String, String> params
...
) {
...
params.get("paramName");
...
return ... 리턴값
}
null
값과 원치 않는 파라미터입력에 대한 보호 로직을 추가해야 한다는 단점이 있을 수 있습니다.@ModelAndAttribute
애노테이션을 사용할 수 있습니다.@GetMapping("/uri")
public ...리턴값 getFoo(
@ModelAndAttribute Foo foo
...
) {
...
return ... 리턴값
}
---
@Getter
@Setter
class Foo {
Bar bar1
Bar bar2
...
}
@ModelAndAttribute
애노테이션을 이용해 객체로 바인딩 할 땐 바인딩 할 객체에 각 파라미터에 대한 필드가 있어야 하고 그 필드들에 대한 Setter
가 모두 존재해야한다는 단점이 있습니다.Pageable
객체로 바인딩 해주는 PageableHandlerMethodArgumentResolver
를 살펴보겠습니다.PageableHandlerMethodArgumentResolver
는 PageableArgumentResolver
인터페이스를 구현하고 PageableArgumentResolver
인터페이스는 HandlerMethodArgumentResolver
를 상속받고 있는걸 확인할 수 있습니다HandlerMethodArgumentResolver
의 주석 내용은 다음과 같습니다.의역)
요청 파라미터를 인자(타입)로 변환하는 전략인터페이스입니다.
boolean suppertsParameter -> 파라미터가 리졸버를 지원하는지를 참/거짓으로 반환합니다.
Object resolveArgument -> 파라미터를 변환해 리턴합니다.
PageableHandlerMethodArgumentResolver
인터페이스로 돌아와서 구현체 로직을 살펴보면 다음과 같습니다.PageableHandlerMethodArgumentResolverSupport
에 정의된 방식대로 Pageable
객체를 만들어 반환합니다.Pageable
객체로 바인딩 해줍니다.공개여부
, 검색타입(제목, 방장아이디 등)
, 키워드
, 페이지번호
, 페이지사이즈
, 정렬기준
, 정렬방향
총 7가지의 파라미터가 필요합니다.Pageable
객체로의 바인딩을 사용해 페이지번호
, 페이지사이즈
, 정렬기준
, 정렬방향
이 4개의 파라미터를 묶어주면 공개여부
, 검색범위(제목, 방장아이디 등)
, 키워드
가 남게됩니다.RoomSearchParam
라는 객체로 바인딩하겠습니다.public record RoomSearchParam(
boolean isPublic,
String type,
String value
) {}
💬 record?
자바 14부터 추가된 데이터 클래스입니다.
final
클래스라 상속할 수 없으며 모든 필드는private final
로 선언됩니다.
각 필드의 이름으로 정의된Getter
메서드와 모든 필드를 초기화하는 생성자가 기본으로 생성됩니다.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RoomSearchRequest {
}
@Target(ElementType.PARAMETER)
: 메서드 파라미터로 선언한 객체에만 지정할 수 있는 애노테이션이라고 선언합니다.@Retention(RetentionPolicy.RUNTIME)
: 애노테이션 정보를 런타임시까지 유지합니다.@Component
public class RoomSearchParamHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final List<String> types = List.of("title", "host");
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(Request.RoomSearchParam.class) &&
parameter.hasParameterAnnotation(RoomSearchRequest.class);
}
@Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) throws Exception {
boolean isPublic = Boolean.parseBoolean(webRequest.getParameter("public"));
String type = webRequest.getParameter("type");
String value = webRequest.getParameter("value");
validateParam(type, value);
return new Request.RoomSearchParam(isPublic, type, value);
}
private void validateParam(String type, String value) {
if ((type == null && value != null) || (type != null && !types.contains(type))) {
throw new IncompatibleParametersException();//TODO: 상세 예외 처리
}
}
}
PageableHandlerMethodArgumentResolver
와는 다르게 supportsParameter()
메서드에서 위에서 생성한 @RoomSearchRequest
애노테이션이 붙어있는지 까지 검사합니다.@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new RoomSearchParamHandlerMethodArgumentResolver());
}
...
}
커스텀 애노테이션을 생성하고 메서드 인자를 바인딩하는 방법을 알아보았습니다.
혹시나 오류가 발생한다거나 결과가 올바르지 않다면 중간에 오탈자는 없었는지, 빠진건 없는지 한번 다시 확인해 주시고 이해가 안되는 부분이 있다면 댓글로 남겨주시면 확인하는 대로 답변 달겠습니다.