요청이 컨트롤러로 들어올 때, 요청에 포함된 값들(Json 등)을 바탕으로 원하는 객체를 만들어내는 작업을 Spring이 ArgumentResolver를 통해 자동으로 처리해줄 수 있다.
그런데 이 ArgumentResolver가 도대체 무엇이길래 그런 일을 할 수 있을까?
또, 이를 통해 무엇을 할 수 있기 때문에 Spring이 이 기능을 지원하는지 알아보자.
실제로 Kicketing 프로젝트에서 사용했던 로직이다.
@GetMapping
public ResponseEntity<UserResponse> getUser(@JwtLogin User user) {
return ResponseEntity.ok(UserResponse.from(user));
}
요청에 포함된 값으로 객체를 생성하는 과정을 ArgumentResolver가 담당하게 되면, 컨트롤러 메서드에서 객체를 직접 파싱하거나 생성할 필요 없이 간결하게 코드를 작성할 수 있다.
위 코드에서도 @JwtLogin
어노테이션을 통해 요청에 포함된 JWT 토큰으로부터 사용자 정보를 파싱하고, 이를 User
객체로 자동으로 매핑해준다.
ArgumentResolver는 이런 방식으로 특정 어노테이션을 분석하고, 그에 맞는 로직을 적용해 객체를 생성하는 역할을 한다.
이 로직을 Kicketing 프로젝트에서 사용했던 이유는, JWT 토큰을 통해 인증된 사용자 정보를 컨트롤러에 손쉽게 주입하고, 이를 활용해 유저 관련 로직을 처리하기 위함이었다.
이렇게 하면 코드가 훨씬 간결해지고, 보일러플레이트 코드가 줄어들어 유지보수성도 향상된다.
만약 ArgumentResolver를 사용하지 않는다면, 아래와 같이 JWT 토큰을 직접 파싱하고, 그 값을 기반으로 객체를 수동으로 생성하는 번거로운 과정을 거쳐야 할 것이다.
@GetMapping
public ResponseEntity<UserResponse> getUser(HttpServletRequest request) {
// JWT 토큰을 쿠키에서 가져옴 (Authorization이라는 이름의 쿠키를 사용)
Cookie[] cookies = request.getCookies();
if (cookies == null) {
throw new TokenExtractionException(JwtTokenType.ACCESS_TOKEN);
}
String token = Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(HttpHeaders.AUTHORIZATION))
.findFirst()
.map(Cookie::getValue)
.orElseThrow(() -> new TokenExtractionException(JwtTokenType.ACCESS_TOKEN));
// 토큰을 검증하고 유저 정보 추출
String email = jwtTokenProvider.extractEmailFromAccessToken(token);
User user = userRepository.findByEmail(email)
.orElseThrow(NoSuchUserException::new);
// 파싱된 유저 정보를 사용해 응답 생성
return ResponseEntity.ok(UserResponse.from(user));
}
위 코드가 매번 반복된다는 것은 매우 비효율적이고, 코드 중복으로 인한 유지보수의 어려움과 실수의 가능성도 높아진다.
같은 로직을 여러 곳에서 반복해서 작성하게 되면 코드의 가독성도 떨어지고, 작은 변경 사항이 생길 때마다 모든 관련 코드를 수정해야 하기 때문에, 관리가 복잡해질 수 있다.
이러한 문제를 해결하기 위해 Spring은 ArgumentResolver를 통해 이러한 공통 작업을 자동화하고, 재사용 가능한 방식으로 처리할 수 있도록 지원하는 것이다.
이제 왜 ArgumentResolver를 사용하는지에 대해 알아본 것 같으니, 이번에는 어떻게 사용하고 어떤 방식으로 동작하기에 이러한 기능이 가능한지 살펴보자.
ArgumentResolver
ArgumentResolver는 HandlerMethodArgumentResolver
를 구현함으로써 시작된다.
Spring에서 설명하는 HandlerMethodArgumentResolver
는 다음과 같다.
Strategy interface for resolving method parameters into argument values in the context of a given.
Spring은 ArgumentResolver를 하나의 전략 인터페이스로 설명하며, 이 인터페이스는 메서드의 파라미터를 특정 조건에 맞게 변환해주는 역할을 한다.
이 인터페이스는 두 가지 주요 메서드를 구현하도록 요구한다
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
간단히 설명하면, 원하는 ArgumentResolver를 실행시키기 위해서는 메서드 파라미터에 특정 어노테이션을 붙이는 방식으로 구현한다.
supportsParameter
는 요청받은 메서드의 인자에 우리가 지정한 어노테이션이 붙어 있는지를 확인하고, 그 어노테이션이 있으면true
를 반환한다.resolveArgument
는supportsParameter
가true
를 반환한 경우, 즉 특정 어노테이션이 붙은 메서드가 있을 때, 해당 파라미터를 원하는 형태로 변환하여 반환하는 메서드다.
이를 통해, Spring은 특정한 어노테이션이 달린 파라미터에 대해 자동으로 값을 매핑하고 처리할 수 있게 한다. 이처럼 ArgumentResolver는 코드의 중복을 줄이고, 유연한 파라미터 처리 방식을 제공하여 개발자의 편의성을 크게 향상시키는 중요한 역할을 한다.
처음 진행했던 MaPhant 프로젝트에서는 ArgumentResolver 같은 기능을 몰랐던 탓에, 중복 코드가 계속해서 들어가는 문제를 겪었다. 커스텀 토큰을 직접 파싱하고 사용자 정보를 가져오는 과정을 매번 수동으로 구현하다 보니 코드가 복잡해지고 유지보수성도 떨어졌다.
이후 Spring의 ArgumentResolver를 알게 되면서, 이러한 작업을 통해 코드의 간결함과 효율성을 크게 개선할 수 있었다.
참고자료
답글 남기기