오늘은 Spring의 @ControllerAdvice 어노테이션에 대해서 알아보도록 하자. 많은 내용은 아니지만 이런기능도 있으니 한번 살펴보도록 하자.
대부분이 Spring 을 사용할 때 @ControllerAdvice를 글로벌 예외처리기로 사용한다. 하지만 @ControllerAdvice 어노테이션은 예외처리기만을 위한 것은 아니다.

아마도 예외처리기로 사용할 때가 많아서 대부분이 예외처리로 사용할 뿐이다.
구글에 @ControllerAdvice을 검색을 해보면 Exception 처리만 수두룩하다. 뭐 틀린말은 아니다. 예외처리로만 사용해도 문제는 없다.
하지만 예외처리뿐만 아니라 다용도로 사용할 수 있으니 알면 좋을 것 같아서 포스팅을 해본다.

흔히 사용하는 예외처리 @ExceptionHandler

대부분이 @ControllerAdvice를 예외처리기로만 사용한다. 필자도 일반적으로 여러분과 비슷하게 @ControllerAdvice를 사용할 때 99%를 예외처리기로 사용한다.

@ControllerAdvice
public class GlobalControllerAdvice {

    @ExceptionHandler(NullPointerException.class)
    public void nullPointerException(NullPointerException e) {
        //blabla
    }

    //...
}

우리는 위와 같이 특정한 exception을 잡아서 처리한다. 아주 좋은 방법이다. exception을 비지니스 로직에 넣지 않고 분리함으로써 비지니스 로직에 좀 더 집중할 수 있게 한다.
아마도 예외처리기로 대다수가 작성하다보니까 @RestControllerAdvice 어노테이션도 추가 된 듯 싶다. @RestControllerAdvice 은 @ControllerAdvice 어노테이션과 @ResponseBody 어노테이션을 합쳐놓은 어노테이션이다. 만약 예외처리를 body로 전달하고 싶다면 @RestControllerAdvice 이용하면 된다.

ModelAttribute

@ControllerAdvice 어노테이션에 사용할 수 있는 어노테이션은 @ModelAttribute 어노테이션이다.
이것 역시 글로벌 하게 사용할 수 있다. @ModelAttribute를 모든 Controller에 사용한다면 @ControllerAdvice에 선언하면 된다.

@RestControllerAdvice
public class GlobalControllerAdvice {

    @ModelAttribute
    public User user() {
        return new User("wonwoo");
    }
}

위와 같이 사용할 경우 ModelAttribute를 글로벌하게 사용한다는 의미이다. 예를들어 Spring security를 사용해서 사용자 정보를 얻고 싶다면 아래처럼 작성하면 좀 더 편리하다.

@ControllerAdvice
public class GlobalControllerAdvice {

    @ModelAttribute
    public User user(@AuthenticationPrincipal User user) {
        return user;
    }
}

매번 Controller에 작성하지 않고 @ControllerAdvice 이용해서 한번만 작성하면 된다. 매번 작성하는 것보다 나은 방법이다.

InitBinder

@ControllerAdvice에 사용할 수 있는 어노테이션이 한가지 더 있다. 바로 @InitBinder 어노테이션이다. @InitBinder 어노테이션은 아주 다양한 설정을 지원한다.
예를들어 Validator, Formatter, Converter, PropertyEditor 등, 뿐만 아니라 여러가지를 설정을 할 수 있는 어노테이션이다. 여기서는 주 목적이 이것들을 설명하는 것이 아니라 생략하겠다.

이런것들 역시 우리는 글로벌하게 설정할 수 있다. 바로 @ControllerAdvice 를 이용하면 된다.

@ControllerAdvice
public class GlobalControllerAdvice {

    @InitBinder
    public void initBinder(DataBinder dataBinder){
       //...
    }
}

위와 같이 사용할 경우에도 역시 글로벌 하게 @InitBinder를 사용할 수 있다. 근데 사실 글로벌하게 사용할 일이 있나 싶기도 하다. Validator, Formatter, Converter 는 해당 컨트롤러만 사용할 일이 많아 해당 컨트롤러에 있는게 더 나은 방법인 것 같다. 또한 Formatter, Converter 경우에는 WebMvcConfigurer 상속받아 설정하는 것이 더 편리해서 굳이 이 방법으로 글로벌하게 설정 할일은 드물 것 같다. 그렇지만 만약 사용할 일이 있다면 이 방법도 존재하니 참고하면 되겠다.

ResponseBodyAdvice, RequestBodyAdvice

이번엔 어노테이션이 아닌 인터페이스이다. @ControllerAdvice를 사용할 수 있는 마지막 인터페이스이다. 해당 인터페이스를 구현하고 @ControllerAdvice 어노테이션을 설정하면 Spring이 자동으로 감지해 해당 인터페이스의 역할에 맞게 실행해 준다.

@ControllerAdvice
public class FooResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return false;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
        Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return null;
    }
}


@ControllerAdvice
public class FooRequestBodyAdvice implements RequestBodyAdvice {

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return false;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return null;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) {
        return null;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) {
        return null;
    }
}

위 처럼 ResponseBodyAdvice, RequestBodyAdvice 인터페이스만 구현해주면 된다. 정확한 사용법은 해당 Spring 문서를 보면 될 것 같다.
간단하게 말하자면 ResponseBodyAdvice 는 body에 쓰기전에 커스텀하게 변경가능하고 RequestBodyAdvice 경우에는 바디를 읽기전, 읽은 후 등에 커스텀하게 Body를 변경가능하다.

간단한 예제로 Jackson 의 @JsonView 어노테이션들이 Spring이 사용하게끔 ResponseBodyAdvice, RequestBodyAdvice 인터페이스를 이용해서 구현되었다.


@PostMapping("/") @JsonView(View.Users.class) public User hello(@JsonView(View.Users.class) @RequestBody User user) { return user; }

@JsonView 사용법은 어렵지 않으니 다른 블로그들을 참고 하면 되겠다. 예전에 쓴 글이 있으나 영 빈약해서..

오늘은 이렇게 Spring 의 @ControllerAdvice 에 대해서 알아봤다.
@ControllerAdvice는 예외처리기 말고도 위와 같이 많은 기능을 제공해주고 있으니 필요하다면 사용해도 괜찮다.