티스토리 뷰
커스텀 애너테이션을 만들게 된 이유
개발을 하다가 RestController 에서 하나의 메서드에 서로 다른 RequestBody를 받고 싶었다.
상황)
어떠한 서비스에 대한 동작을 제어하는 메서드 였으며, 동작제어 (시작, 종료) 에 따라, RequestBody에 포함하는 속성이 조금 달랐다.
1안)
동작제어 별로 메서드 따로 둠.
=> 고민을 하긴 했는데, 동일한 대상에 속성 값만 달라지는데, 메서드를 따로 둘만큼 기능 상에 큰 차이가 있다고 생각하지 않았기 때문에 일단 패스하였다.
2안)
메서드 파라미터에 @RequestBody 로 바로 타입 변환하지 않고, HttpRequestServlet을 이용하여, 동작제어에 맞는 타입으로 직접 변환
=> 원래 하고자 했던 기능을 구현할 수 있다. 하나의 메서드에 서로 다른 동작에 따라 다른 RequestBody를 받을 수 있다. 그래서 이 방법을 채택하여 진행하였다.
하단은 그 결과에 따른 코드이다.
@PatchMapping("[경로]")
public Response<Void> exec( ....,
HttpServletRequest request){
//HttpServletRequest 에서 body 추출
StringBuilder sb = new StringBuilder();
try(BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()))){
String tmp = "";
while( (tmp = br.readLine())!= null){
sb.append(tmp);
}
}catch(IOException e){
throw new InvalidRequestException(ErrorCode.INVALID_REQUEST, ErrorCode.INVALID_REQUEST.getMessage());
}
//추출한 body를 JsonObject로 변환
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(sb.toString(), JsonObject.class);
//동작제어에 따른 서로 다른 객체로 타입 변환
String requestStatus = jsonObject.get("status").getAsString();
if("START".equals(requestStatus)){
StartRequest startRequest = new StartRequest(Status.START, jsonObject.get("id").getAsLong());
...
}else if("STOP".equals(requestStatus)){
StopRequest stopRequest = new StopRequest(Status.STOP);
...
}else{
throw new InvalidRequestException(ErrorCode.INVALID_REQUEST, ErrorCode.INVALID_REQUEST.getMessage());
}
...
}
위 코드는 동작에 따라 각 기능을 수행하기까지, 앞서서 처리해야할 일이 많다.
1. HttpServletRequest 에서 body를 추출
2. 동작을 판별에 따른 객체 타입을 다르게 하기 위한, 동작 확인을 위해 JsonObject로 변환
3. 원래 하고자 했던 동작에 따른 기능 수행
그래서 이를 @RequestBody가 타입 변환해주는 것처럼, 동작에 맞는 타입변환 애너테이션을 만들면 간단하게 원래 기능을 수행할 수 있을 것이라고 생각했다.
커스텀 애너테이션을 이용하여, @RequestBody 처럼 request의 body를 객체로 변환하기
1. @interface 선언
2. 구현체 만들기
3. 등록하기
4. 사용하기
1. @Interface 선언
하단은 ExecRequestBody 명의 애너테이션을 선언한 코드이다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface ExecRequestBody {
}
2. 구현체 만들기
※ 하단의 코드에서 StartRequest와 StopRequest를 ExecRequest를 상속받는다.
@Component
public class ExecRequestBodyArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(ExecRequestBody.class) != null;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//HttpServletRequest에서 body 추출
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
StringBuffer sb = new StringBuffer();
try(BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()))){
String tmp = "";
while( (tmp = br.readLine()) != null){
sb.append(tmp);
}
}catch(IOException e){
return null;
}
return convertToObject(sb.toString());
}
//Request Body를 동작에 따른 객체로 변환
private ExecRequest convertToObject(String str){
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(str, JsonObject.class);
if(jsonObject.get("status") != null
&& jsonObject.get("status").getAsString().equals("START")
&& jsonObject.get("id") != null){
....
return new StartRequest(Status.START, id);
}else if(jsonObject.get("status") != null
&& jsonObject.get("status").getAsString().equals("STOP")){
...
return new StopTrainingRequest(Status.ModelTraining.TERMINATED);
}
throw new InvalidRequestException(ErrorCode.INVALID_REQUEST, ErrorCode.INVALID_REQUEST.getMessage());
}
3. 등록하기
@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final ExecRequestBodyArgumentResolver execRequestBodyArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(execRequestBodyArgumentResolver);
WebMvcConfigurer.super.addArgumentResolvers(resolvers);
}
}
4. 사용하기
메서드 파라미터로 앞서 구현한 커스텀 애너테이션인 @ExecRequestBody를 붙여주어 사용하면 된다.
@PatchMapping("[경로]")
public Response<Void> exec( ...,
@ExecRequestBody ExecRequest request) {
if (request instanceof StartRequest) {
//Start일 때의 로직
} else if (request instanceof StopRequest) {
//Stop일 때의 로직
}
...
}
Reference
https://growing-up-constantly.tistory.com/53
https://shinsunyoung.tistory.com/83
본 글에서 확인한 것처럼 커스텀 애너테이션을 사용했을 때 더욱 명확하게 기능을 파악할 수 있었다.
※ 커스텀 애너테이션 사용은 코드 분석에 어려움을 주기 때문에, 반드시 필요한 경우가 아니라면, 남용해서는 안된다.
'Spring' 카테고리의 다른 글
[ spring-rest-docs ] prettyPrint()를 setUp 메서드에 적용하기 (0) | 2022.07.12 |
---|---|
[ jackson ] ZonedDateTime 으로 역직렬화 시, 에러 발생 (0) | 2022.04.21 |
[Spring Rest Docs ] Spring Rest Docs 적용할 때 발생한 문제 및 해결 방법 (0) | 2022.04.18 |
[ MockMvc ] MockMvc를 이용하여 Controller 테스트 시, 한글 깨짐 해결 방법 (0) | 2022.04.15 |
[ Jackson ] Jackson 사용 시, 발생한 타입 문제 (0) | 2022.04.15 |