스프링을 사용할 때 @ModelAttribute, @RequestBody 등 어노테이션을 컨트롤러 단에서 메서드의 매개변수로 넣어서 많이 사용해왔다.
위의 어노테이션들이 메서드의 매개변수로만 쓰이는 이유도 모른채 기계적으로 사용해온 나를 반성한다...
커스텀한 어노테이션을 만들고 이를 메서드의 매개변수에서 활용하면서 관련된 개념을 짚고 넘어가고자 한다.
HandlerMethodArgumentResolver
HandlerMethodArgumentResolver는 @PathVariable, @RequestParam와 같은 메서드의 여러 인자 값을 처리해주는 역할을 한다. 스프링 프레임워크가 제공하는 인터페이스이다.
참고로 @PathVariable 어노테이션은 PathVariableMethodArgumentResolver가 처리해주고 있으며 @RequestParam은 RequestParamMethodArgumentResolver가 처리해주고 있다.
이처럼 내가 당연히 사용해왔던 어노테이션들은 내부적으로 ArgumentResolver가 처리해주고 있었다.
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter var1);
@Nullable
Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}
HandlerMethodArgumentResolver 인터페이스에 정의된 메서드는 2개 뿐이다.
따라서 HandlerMethodArgumentResolver를 구현한 클래스는 2개의 메서드만 구현해주면 된다.
HandlerMethodArgumentResolver > supportsParameter 메서드
supportsParameter는 컨트롤러의 메서드에 어노테이션이 포함되어 있는지 확인하는 역할을 한다.
만약 포함이 되어 있다면 아래 메서드인 resolveArgument를 실행하고 포함되어 있지 않다면 실행하지 않는다.
supportsParamteter는 HandlerMethodArgumentResolverComposite.getArgumentResolver 메서드에서 실행된다.
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) { //true이면 resolveArgument를 결과로 반환함
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
HandlerMethodArgumentResolver > resolveArgument 메서드
resolveArgument는 메서드에 어노테이션이 포함되어 있을 때 처리하고 싶은 로직을 작성하면 이를 처리해주는 메서드이다.
이제 예시를 들어 쉽게 이해해보자.
우선 커스텀 어노테이션인 @Student를 만들어보도록 하자.
package com.example.demo.annotation;
// import 생략
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentProcess {
String name() default "";
int age() default 0;
}
Target은 파라미터로 설정하여 다른 곳이 아닌 메서드의 파라미터에서만 사용할 수 있도록 했다.
Retention은 런타임으로 설정하여 자바 런타임이 어노테이션에 접근할 수 있도록 했다.
Retention에서는 RetentionPolicy.SOURCE, RetentionPolicy.CLASS, RetentionPolicy.RUNTIME이 존재한다.
1. RetentionPolicy.SOURCE
- 컴파일이 되면 사라지는 어노테이션이다. (소스상에서만 확인할 수 있다)
2. RetentionPolicy.RUNTIME
- 자바 런타임이 어노테이션에 접근할 수 있고 추가적인 작업이 가능하다.
3. RetentionPolicy.CLASS
- 클래스 파일(바이트 코드)에 어노테이션이 존재하지만 자바 런타임이 접근할 수는 없다.
https://blog.baesangwoo.dev/posts/java-livestudy-12week/
참고
name의 기본 값은 "", age는 0을 설정했다.
이제 StudentArgumentResolver를 만들어 보도록 하자.
public class StudentArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(Student.class) != null; // true를 반환시 아래 resolveArgument 메서드를 실행한다.
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
Student student = new Student();
student.setName("Kevin");
student.setAge(10);
return student;
}
}
StudentArgumentResolver를 만들면 끝이 아니다. resolver를 만든 이후에는 이것을 스프링에게 알려주어야 한다.
방법은 아래와 같다.
package com.example.demo.config;
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new StudentProcessArgumentResolver());
}
}
WebMvcConfigurer 인터페이스를 implements하고 addArgumentResolvers를 구현하면 된다.
이제 컨트롤러에서 @StudentProcess 어노테이션을 사용해보도록 하자.
@RequestMapping(value = "/user/annotation-test", method = RequestMethod.GET)
@ResponseBody
public Student annotationTest(@StudentProcess Student student) {
return student;
}
annotationTest 메서드에 @StudentProcess 어노테이션을 매개변수로 넣으면 내가 설정했던 argumentResolver가 실행되어 kevin과 10이 담긴 Student 객체를 반환할 것이다.
예시가 간단했지만 복잡하고 중복이 많은 코드를 위의 방식으로 활용한다면 시간을 최소화할 수 있을 뿐만 아니라 코드의 가독성도 높아질 것이라 생각한다.
참고
'Programming > Spring Framework' 카테고리의 다른 글
[스프링부트] MockMvc Test 2가지 방법 (0) | 2021.11.27 |
---|---|
[스프링부트] Tiles, JSP 사용을 위한 Dependency 설정 및 JSP 수정사항 자동 반영하기 (0) | 2021.11.06 |
[스프링부트] AWS S3에 파일 업로드 하기 (0) | 2021.06.05 |
[스프링부트] fetch api post 전송 방법 (0) | 2021.05.13 |
[스프링부트] @JsonNaming, @JsonProperty는 언제 사용할까? (1) | 2021.05.12 |