티스토리 뷰

반응형

커스텀 애너테이션을 만들게 된 이유 

 

개발을 하다가 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

 

[Spring] RequestParam값 객체로 매핑하기, Custom Annotation 만들기

스프링 Controller에서 Request값을 도메인객체를 사용해서 받을 때는 Json으로 넘어오는 경우에는 requestBody로 받으면 손쉽게 해결할 수 있었다. 위의 상황을 코드로 이야기하자면 다음과 같다. @Reques

growing-up-constantly.tistory.com

https://shinsunyoung.tistory.com/83

 

Spring Boot Custom Annotation 만들기

안녕하세요! 이번 포스팅에서는 Spring Boot에서 Custom Annotation을 만드는 방법에 대해 알아보겠습니다. 전체 코드는 Github에서 확인이 가능합니다. ✍️ 📚 개념 정리 1. 커스텀 어노테이션이란? 프

shinsunyoung.tistory.com


본 글에서 확인한 것처럼 커스텀 애너테이션을 사용했을 때 더욱 명확하게 기능을 파악할 수 있었다. 

 

※ 커스텀 애너테이션 사용은 코드 분석에 어려움을 주기 때문에, 반드시 필요한 경우가 아니라면, 남용해서는 안된다.

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함