ReturnValueHandler 확장해서 Controller 공통 응답 메시지 처리 관심사 분리하기

wanuki·2023년 10월 3일
0

문제 인식

컨트롤러의 테스트 코드를 작성하는데 의존 관계 문제로 예외가 발생하였다.

우리 프로젝트에서는 공통 응답 메시지를 BaseController라는 부모 클래스에서 처리하고,
모든 Controller 는 BaseController클래스를 상속받아 응답 메시지 처리 메서드를 호출하여 응답 값으로 변환하였다.

때문에 Controller에서는 필요하지 않은 의존성이지만 BaseController에서 공통 응답 메시지 처리를 위해 필요로 하는 의존성때문에 예외가 발생한 것이다.

테스트 코드가 신호를 보냈기 때문에 HandlerMethodReturnValueHandler 확장하여 공통 응답 메시지를 처리하는 관심사를 컨트롤에서 분리하고 컨트롤러의 상속을 제거하여 문제를 해결하였다.

ReturnValueHandler 확장하여 문제 해결하기

어노테이션 추가

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ResponseStatus
public @interface ApiResponseBody {
}
  • Controller에 @ResponseBody 어노테이션 대신 @ApiResponseBody 어노테이션을 클래스 또는 메서드에 추가하면 ApiResponseResolver에서 공통 응답 메시지처리를 수행한다.

HandlerMethodReturnValueHandler 인터페이스 확장 클래스 구현

@Setter
public class ApiResponseResolver implements HandlerMethodReturnValueHandler {

    private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(
                returnType.getContainingClass(), ApiResponseBody.class) ||
                returnType.hasMethodAnnotation(ApiResponseBody.class));
    }

    @Override
    public void handleReturnValue(Object returnValue, 
                                  MethodParameter returnType, 
                                  ModelAndViewContainer mavContainer, 
                                  NativeWebRequest webRequest) throws Exception {
	    // 공통 응답 메시지 처리 로직
        // ...
        requestResponseBodyMethodProcessor.handleReturnValue(responseDto,
                returnType,
                mavContainer,
                webRequest);
    }
}
  • supportsReturnType
    • 클래스 또는 메서드에 @ApiResponseBody가 있으면 handleReturnValue 메서드가 실행된다.
  • handleReturnValue
    • 실제 공통 응답 메시지 처리 로직을 실행하는 메서드
    • 응답 메시지 가지고 있는 responseDto 객체를 requestResponseBodyMethodProcessor.handleReturnValue 메서드에 전달하여 객체를 josn응답 값으로 변환한다.

WebMvcConfigurer에 ReturnValueHandler 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        handlers.add(new ApiResponseResolver());
    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;
        ApiResponseResolver apiResponseResolver = null;

        for (HandlerExceptionResolver resolver : resolvers) {
            if (resolver instanceof ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver) {
                HandlerMethodReturnValueHandlerComposite handlerMethodReturnValueHandlerComposite = exceptionHandlerExceptionResolver.getReturnValueHandlers();
                if (handlerMethodReturnValueHandlerComposite == null) {
                    continue;
                }

                for (HandlerMethodReturnValueHandler handler : handlerMethodReturnValueHandlerComposite.getHandlers()) {
                    if (handler instanceof RequestResponseBodyMethodProcessor processor) {
                        requestResponseBodyMethodProcessor = processor;
                    }
                    if (handler instanceof ApiResponseResolver responseResolver) {
                        apiResponseResolver = responseResolver;
                    }
                }

                if (apiResponseResolver != null) {
                    apiResponseResolver.setRequestResponseBodyMethodProcessor(requestResponseBodyMethodProcessor);
                }
            }
        }
    }
}
  • addReturnValueHandlers
    • 매개변수 resolvers는 null이다.
    • 우리가 만든 ApiResponseResolver 클래스를 HandlerMethodReturnValueHandler로 등록한다.
  • extendHandlerExceptionResolvers
    • 매개변수 resolvers는 null이 아니고 스프링이 등록한 객체 외에도 우리가 등록한 ApiResponseResolver객체도 포함되어 있다.
    • HandlerExceptionResolver 리스트 중에는 ExceptionHandlerExceptionResolver 클래스가 존재한다.
    • ExceptionHandlerExceptionResolver클래스는 HandlerMethodReturnValueHandlerComposite클래스를 필드로 가지고 있다.
    • HandlerMethodReturnValueHandlerComposite클래스는 HandlerMethodReturnValueHandler리스트를 필드로 가지고 있다.
    • HandlerMethodReturnValueHandler 리스트 중에는 우리가 등록한 ApiResponseResolver와 스프링이 등록한 RequestResponseBodyMethodProcessor가 존재한다.
    • 우리가 등록한 ApiResponseResolver에 RequestResponseBodyMethodProcessor를 주입한다.

결과

컨트롤러에서 공통 응답 메시지 처리 관심사를 ReturnValueHandler를 확장하여 분리하였고, 이로 인해서 부모 클래스와의 결합을 제거하였고 컴포넌트간의 결합도를 낮출 수 있었다.

profile
하늘은 스스로 돕는 자를 돕는다

0개의 댓글