Spring의 Argument Resolver는 Spring MVC에서 컨트롤러 메서드의 argument를 해석하고 객체로 변환하는 데 사용되는 기술이다. 이를 통해 요청 파라미터, 세션 정보, 쿠키, 헤더 등의 다양한 웹 요청 데이터를 컨트롤러 메서드의 인수로 쉽게 매핑할 수 있다.
HandlerMethodArgumentResolver는 요청 데이터를 메서드의 매개변수로 변환할 때 사용하는 전략 인터페이스다. 컨트롤러의 메서드가 호출될 때 매개변수에 전달할 객체를 생성하거나 조작하는 로직을 구현할 수 있다.
두 개의 주요 메서드가 있다.
supportsParameter(MethodParameter parameter)
: 해당 인수 타입을 이 Argument Resolver가 지원하는지 여부를 반환한다.resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
: 인수를 실제로 해석하고 변환한다.Spring MVC는 여러 기본 Argument Resolver를 제공한다.
RequestParamMethodArgumentResolver
: @RequestParam
으로 주석이 달린 인수를 해석한다.PathVariableMethodArgumentResolver
: @PathVariable
로 주석이 달린 인수를 해석한다.ModelAttributeMethodProcessor
: @ModelAttribute
로 주석이 달린 인수를 해석하고 모델에 추가한다.특정 요구 사항에 맞게 커스텀 Argument Resolver를 구현할 수 있다. 예를 들어, 요청 헤더에서 사용자 정보를 추출하는 커스텀 Argument Resolver를 만들어보자.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
private final RequestHandler requestHandler;
private final MemberService memberService;
private final AuthService authService;
public LoginUserArgumentResolver(RequestHandler requestHandler, MemberService memberService,
AuthService authService) {
this.requestHandler = requestHandler;
this.memberService = memberService;
this.authService = authService;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(LoginUser.class);
}
@Override
public AuthInfo resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
return AuthInfo.from(memberService.findById(authService.fetchByToken(requestHandler.extract(request)).getId()));
}
}
requestHandler 객체를 사용하여 HTTP 요청에서 토큰을 추출하여 authService에서 인증 정보를 조회한다. 인증 정보 객체에서 사용자 ID를 가져와 memberService를 사용하여 사용자 ID로 사용자 정보를 조회합니다. 조회한 사용자 정보를 사용하여 AuthInfo 객체를 생성하여 반환한다.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final LoginUserArgumentResolver loginUserArgumentResolver;
public WebMvcConfig(LoginUserArgumentResolver loginUserArgumentResolver) {
this.loginUserArgumentResolver = loginUserArgumentResolver;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginUserArgumentResolver);
}
}
@RestController
@RequestMapping("/api/v1/reservations")
public class ReservationController {
private final ReservationApplicationService reservationApplicationService;
public ReservationController(ReservationApplicationService reservationApplicationService) {
this.reservationApplicationService = reservationApplicationService;
}
@PostMapping
public ResponseEntity<ReservationResponse> create(@LoginUser AuthInfo authInfo,
@RequestBody @Valid ReservationRequest reservationRequest) {
ReservationResponse response = reservationApplicationService.createMemberReservation(reservationRequest);
return ResponseEntity.created(URI.create("/reservations/" + response.memberReservationId())).body(response);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@LoginUser AuthInfo authInfo,
@PathVariable("id") @Min(1) long reservationMemberId) {
reservationApplicationService.deleteMemberReservation(authInfo, reservationMemberId);
return ResponseEntity.noContent().build();
}
@GetMapping("/my")
public ResponseEntity<List<MyReservationResponse>> getMyReservations(@LoginUser AuthInfo authInfo) {
List<MyReservationResponse> responses = reservationApplicationService.findMyReservations(authInfo)
.stream()
.map(MyReservationResponse::from)
.toList();
return ResponseEntity.ok(responses);
}
}
이와 같은 방식으로 커스텀 Argument Resolver를 사용하여, Spring MVC 컨트롤러 메서드에서 특정 타입의 인수를 자동으로 해석하고 변환할 수 있다. 이는 코드의 재사용성을 높이고, 컨트롤러의 비즈니스 로직을 간결하게 유지하는 데 도움이 된다.