[Spring Boot] @RestControllerAdvice 란
들어가며
자사 서드파티 API를 개발하는 업무를 담당했을 때, 처음에는 모든 예외처리를 try-catch로 처리하였다. 그렇다 보니 불필요한 중복 코드들이 많아지고 가독성도 떨어졌다. 또, 코드가 점점 복잡해져 생산성도 떨어졌다. 확실한 건 중복되는 코드들이 너무 많았다. 이러한 문제를 해결하기 위해 고민하고 찾아본 결과 @ControllerAdvice, @RestControllerAdvice를 발견했다.
@ControllerAdvice 란
/**
* Specialization of {@link Component @Component} for classes that declare
* {@link ExceptionHandler @ExceptionHandler}, {@link InitBinder @InitBinder}, or
* {@link ModelAttribute @ModelAttribute} methods to be shared across
* multiple {@code @Controller} classes.
*
* <p>Classes annotated with {@code @ControllerAdvice} can be declared explicitly
* as Spring beans or auto-detected via classpath scanning. All such beans are
* sorted based on {@link org.springframework.core.Ordered Ordered} semantics or
* {@link org.springframework.core.annotation.Order @Order} /
* {@link javax.annotation.Priority @Priority} declarations, with {@code Ordered}
* semantics taking precedence over {@code @Order} / {@code @Priority} declarations.
* {@code @ControllerAdvice} beans are then applied in that order at runtime.
* Note, however, that {@code @ControllerAdvice} beans that implement
* {@link org.springframework.core.PriorityOrdered PriorityOrdered} are <em>not</em>
* given priority over {@code @ControllerAdvice} beans that implement {@code Ordered}.
* In addition, {@code Ordered} is not honored for scoped {@code @ControllerAdvice}
* beans — for example if such a bean has been configured as a request-scoped
* or session-scoped bean. For handling exceptions, an {@code @ExceptionHandler}
* will be picked on the first advice with a matching exception handler method. For
* model attributes and data binding initialization, {@code @ModelAttribute} and
* {@code @InitBinder} methods will follow {@code @ControllerAdvice} order.
*
* <p>Note: For {@code @ExceptionHandler} methods, a root exception match will be
* preferred to just matching a cause of the current exception, among the handler
* methods of a particular advice bean. However, a cause match on a higher-priority
* advice will still be preferred over any match (whether root or cause level)
* on a lower-priority advice bean. As a consequence, please declare your primary
* root exception mappings on a prioritized advice bean with a corresponding order.
*
* <p>By default, the methods in an {@code @ControllerAdvice} apply globally to
* all controllers. Use selectors such as {@link #annotations},
* {@link #basePackageClasses}, and {@link #basePackages} (or its alias
* {@link #value}) to define a more narrow subset of targeted controllers.
* If multiple selectors are declared, boolean {@code OR} logic is applied, meaning
* selected controllers should match at least one selector. Note that selector checks
* are performed at runtime, so adding many selectors may negatively impact
* performance and add complexity.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Sam Brannen
* @since 3.2
* @see org.springframework.stereotype.Controller
* @see RestControllerAdvice
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
@ControllerAdvice는 @ExceptionHandler, @ModelAttribute, @InitBinder가 적용된 메소드들에 AOP를 적용하여 Controller단에 적용하기 위해 고안된 어노테이션이다. 클래스에 선언되며, 모든 @Controller에 대한 전역적으로 발생할 수 있는 예외를 잡아서 처리할 수 있다.
@RestControllerAdvice 란
/**
* A convenience annotation that is itself annotated with
* {@link ControllerAdvice @ControllerAdvice}
* and {@link ResponseBody @ResponseBody}.
*
* <p>Types that carry this annotation are treated as controller advice where
* {@link ExceptionHandler @ExceptionHandler} methods assume
* {@link ResponseBody @ResponseBody} semantics by default.
*
* <p><b>NOTE:</b> {@code @RestControllerAdvice} is processed if an appropriate
* {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
* {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter} pair
* which are the default in the MVC Java config and the MVC namespace.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 4.3
* @see RestController
* @see ControllerAdvice
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
@RestControllerAdvice는 @ControllerAdvice와 @ResponseBody를 합친 어노테이션이다. @ControllerAdvice의 역할을 수행하고, @ResponseBody를 통해 객체를 리턴할 수 있다.
따라서 단순히 예외 처리를 하고 싶다면 @ControllerAdvice를, 응답으로 객체를 리턴해야 한다면 @RestControllerAdvice를 적용하면 된다.
위 두 어노테이션 모두 적용 범위를 클래스나 패키지 단위로 제한할 수 있으며, 아래와 같이 사용하면 된다.
@RestControllerAdvice(basePackageClasses = TestExceptionController.class)
public class ApiExceptionAdvice {
// ...
}
@RestControllerAdvice(basePackages = "com.jdh.restControllerAdvice.controller")
public class ApiExceptionAdvice {
// ...
}
@ExceptionHandler 란
@ExceptionHandler 어노테이션을 메소드에 선언하고 특정 예외 클래스를 지정해주면 해당 예외가 발생했을 때 메소드에 정의한 로직으로 처리할 수 있다. @ControllerAdvice 또는 @RestControllerAdvice에 정의된 메소드가 아닌 일반 컨트롤러 단에 존재하는 메소드에 선언할 경우, 해당 Controller에만 적용된다.
@Service 등의 빈에서는 적용되지 않는다.
정리하며
이번 게시글에선 @ControllerAdvice, @RestControllerAdvice에 대해 알아보았다. 다음 게시글에선 해당 어노테이션을 사용하여 예외 처리하는 방법에 대해서 알아보겠다.
참고