Spring Boot의 예외 처리는 @RestControllerAdvice 를 사용한다.

 

@RestControllerAdvice = ControllerAdvice + ResponseBody 

 

따라서, ResponseBody와 같이 리턴하는 응답 값이 body로 설정되어 클라이언트에게 전달된다.

아래와 같이 IndexOutOfBoundsException을 발생하도록 설정하고 HTTP Get요청을 보내면 아래 그림과 같이

Http Status Code 500번과 Internal Server Error이 반환된다. 

 

@Slf4j
@RestController
@RequestMapping("/api")
public class RestApiBController {

    @GetMapping(path = "")
    public void hello() {
        var list = List.of("hello");

        var element = list.get(1);

        log.info("element : {}", element);
    }
}

 

 

예외가 발생해도 자연스럽게 응답을 내려주기 위해서는 서버에서 컨트롤러에 대한 예외를 처리해야 한다.

try catch로 직접 처리할 수 있지만 코드 길이가 길어질 수 있으므로 @RestControllerAdvice 를 사용한 Handler 클래스를 만들어서 사용한다.

 

@RestControllerAdvice 가 붙은 클래스는 Rest API Controller에서 예외가 일어나는 것을 감지

@ExceptionHandler로 어떠한 예외를 캐치할 것인지 선언 가능, 아래의 코드에선 (IndexOutOfBoundsException)을 value 값으로 설정했으므로 해당 예외를 처리한다.

 

@Slf4j
@RestControllerAdvice
//-> 아래 클래스는 rest api를 사용하는 곳에 에러를 감지함
public class RestApiExceptionHandler {

    @ExceptionHandler(value = { IndexOutOfBoundsException.class })
    public ResponseEntity outOfBound(
            IndexOutOfBoundsException e
    ){
        log.error("IndexOutOfBoundsException",e);
        return ResponseEntity.status(200).build();
    }
}

 

ResponseEntity를 반환하기에 HttpStatus 코드를 200으로 변경하여 반환하면 이전과 다르게 예외가 핸들링된 것을 볼 수 있다. @ExceptionHandler(value = { IndexOutOfBoundsException.class }) 의 value 값을 Exception.class로 바꾸게 되면 전체적인 Exception 하위의 예외들을 한 번에 처리할 수 있다.

 

 

다른 방법으로 따로 ExceptionHandler을 관리하지 않고, 컨트롤러 단에서 직접 @ExceptionHandler을 사용하여 해결할 수 있다. 아래 코드는 위와 같은 예제에서 Controller 내부에서 @ExceptionHandler 를 적용한 코드이다. 코드 길이가 길어질 수 있으므로  이 방법은 특별한 컨트롤러에서만 사용을 한다.

 

@Slf4j
@RestController
@RequestMapping("/api/b")
public class RestApiCController {

    @GetMapping("")
    public void hello() {
        throw new NumberFormatException("number format exception");
    }

    @ExceptionHandler(value = NumberFormatException.class) //->글로벌로 가지 않고 여기서 해결
    public ResponseEntity numberFormatException(
            NumberFormatException e
    ){
        log.error("RestApiCController", e);
        return ResponseEntity.ok().build();
    }
}

 

 value 값에 클래스를 명시하는 것 이외에도 basePackages를 명시할 수 있다. @RestControllerAdvice 의 value값에 패키지 경로를 명시하게 되면 해당 패키지에 속한 컨트롤러에서 일어나는 모든 예외를 하나의 핸들러로 관리할 수 있다.

 

@Slf4j
@RestControllerAdvice(basePackages = "com.example.exception.controller") // 패키지 명시
@Order(1)
public class RestApiExceptionHandler {

    @ExceptionHandler(value = { IndexOutOfBoundsException.class })
    public ResponseEntity outOfBound(
            IndexOutOfBoundsException e
    ){
        log.error("IndexOutOfBoundsException",e);
        return ResponseEntity.status(200).build();
    }

    @ExceptionHandler(value = NoSuchElementException.class)
    public ResponseEntity<Api> noSuchElement(
            NoSuchElementException e
    ){
        log.error("",e);

        var reponse = Api.builder()
                .resultCode(String.valueOf(HttpStatus.NOT_FOUND))
                .resultMessage(HttpStatus.NOT_FOUND.getReasonPhrase())
                .build();

        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(reponse);
    }
}

 

 

ExceptionHandler가 2개 이상인 경우 @Order을 통해 먼저 처리될 수 있는, 우선순위를 부여할 수 있다.

 

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

 

@Slf4j
@RestControllerAdvice(basePackages = "com.example.exception.controller") // 패키지 명시
@Order(1)
public class RestApiExceptionHandler {
@Order <<<< spec >>>>
int value() default Ordered.LOWEST_PRECEDENCE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

@Order의 우선순위를 설정할 때 기본 값은  LOWEST_PRECEDENCE = MAX_VALUE 로 설정되어 있다.

@Order가 있을 경우,  value가 낮은 친구 먼저 처리된다.

 

Order     vs  Order(1) : default가 MaxInt이므로 그보다 낮은 1이 실행

Order(2) vs Order(3) : 2가 실행

Order      vs Order : Class 이름 순으로 실행 

먼저 처리되고 싶으면 value에 낮은 값을, 나중에 처리되고 싶으면 높은 값을 주면 된다.

 

위의 GlobalExceptionHandlerRestApiExceptionHandler에서 (처리하지 못한, 예상치 못한) 나머지 예외를 받아내기 위한 최후의 역할을 하므로RestApiExceptionHandler 의 Order(value)값 보다 높게 값을 설정하여 RestApiExceptionHandler가 먼저 처리되게 한다.

 

 

(위의 예시 상황은 Oder가 없는 경우 vs 있는 경우임... 만약 Order가 둘 다 있을 경우엔 값이 낮은 친구부터)