Spring

예외처리 - @ExceptionHander, @ControllerAdvice, @RestConrollerAdvice

웹 어플리케이션 개발에 있어서 예측 가능한 유저의 행동을 제어하고 서버가 다운되는 것을 막기 위해 여러가지 예외처리가 필요하다.

 

예외 처리 하는 다양한 방법

  1. 메소드 내에서 예외 상황을 예측해서 처리하는 try-catch 문을 이용하는 방법
  2. 요구사항에 의한 예외처리 (
    • ex ) validation : 특정값이 0 ~ 255범위가 아니면 유효하지 않은 값으로 판단하고 예외처리
  3. 스프링 시큐리티에서 인터셉터로 잡아서 UnautorizedException 같은 예외 처리

 

예외 처리 적용의 문제

예외처리를 적용하다 보면 코드가 엄청나게 복잡해지고, 코드가 복잡해지면 유지보수하기 어려워진다.
비즈니스 로직에 집중하기 어렵고, 비즈니스 로직과 관련된 코드보다 예외 처리를 위한 코드가 더 많아지는 경우도 생긴다.

 

이런 문제를 조금이라도 개선하기위해 @ExceptionHandler@ContollerAdvice를 사용한다.


@ExceptionHandler

@Controller, @RestController가 적용된 Bean 내에서 발생하는 예외를 잡아서 하나의 메소드로 처리해주는 기능을 한다.

  • @Controller, @RestController에만 적용가능 (@Service같은 빈 불가)
  • 리턴 타입과 관계없이 동작
  • 메소드 파라미터 자유롭게 가능
  • 어노테이션에 등록된 파라미터 Exception만 처리 가능
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {

    /**
     * Exceptions handled by the annotated method. If empty, will default to any
     * exceptions listed in the method argument list.
     */
    Class<? extends Throwable>[] value() default {};

}

사용 방법

  • value 설정을 통해 어떤 예외를 잡을지 설정할 수 있다.
  • value를 지정하지 않으면 모든 예외를 잡기때문에 필요한 예외를 설정해주어야한다.

@ExceptionHandler 에 인자로 캐치하고 싶은 예외 클래스를 등록

@RestController 
public class ExampleController{ 
    @ExceptionHandler(value = NullPointerException.class) 
    public ResponseEntity<String> handle(IOException ex) {
         ...
    }
}

ExampleController에 해당하는 Bean에서 NullException이 발생한다면,
@ExceptionHandler(value = NullPointerException.class)가 적용된 메소드가 호출된다. (’value =’ 생략 가능)

예외 클래스 두 개 이상 등록도 가능하다.

@ExceptionHandler({Exception1.class, Exception2.class})

@ControllerAdvice

@ExceptionHandler가 하나의 클래스에 대한 것이라면, @ControllerAdvice는 모든 @Controller 전역에서 발생할 수 있는 예외를 잡아 처리해주는 Annotation이다.

@ControllerAdvice

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    //...
}

클래스에 @ControllerAdvice 를 붙여주고, @ExceptionHandler로 처리하고 싶은 예외를 잡아 처리하면 된다. 별도의 속성값 없이 사용하면 모든 패키지 전역에 있는 컨트롤러를 담당하게된다.

@ControllerAdvice
public class PageControllerAdvice{
    @ExceptionHandler(SQLException.class)
    public ResponseEntity<String> dataExceptionHandle() {
        return ResponseEntity.badRequest().build();
    }

    @ExceptionHandler(CustomException.class)
    public String custom() {
        return "customException";
    }
}

@RestControllerAdvice

@ControllerAdvice + @ResponseBody → @RestControllerAdvice

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
@ControllerAdvice 
@ResponseBody 
public @interface RestControllerAdvice { 
    //... 
}

@RestControllerAdvice를 들여다보면 @ControllerAdvice와 동일하게 예외를 잡아 핸들링을 할 수 있는 기능을 수행하면서 @ResponseBody를 통해 응답의 body에 객체를 넣어 리턴 할 수 있다.

💡 @RestController에서 예외가 발생하든 @Controller에서 예외가 발생하든 @ControllerAdvice + @ExceptionHandler 조합으로 다 캐치할 수 있기에, @ResponseBody의 필요 여부에 따라 적용하면 된다.

전역의 예외를 잡긴하되 패키지 단위로 제한할 수도 있다.

@RestControllerAdvice("com.example.demo.login.controller")

| Reference

https://velog.io/@hanblueblue/Spring-ExceptionHandler

https://jeong-pro.tistory.com/195

https://tecoble.techcourse.co.kr/post/2021-05-10-controller_advice_exception_handler/