
@ExceptionHandler는 Controller 계층에서 발생하는 에러를 잡아서 메서드로 처리해주는 기능으로, 매우 유연하게 에러를 처리할 수 있는 방법을 제공한다.@ExceptionHandler는 다음에 어노테이션을 추가함으로써 에러를 손쉽게 처리할 수 있다.@ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드@RestController
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/product/{id}")
public Response getProduct(@PathVariable String id){
return productService.getProduct(id);
}
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElementFoundException(NoSuchElementFoundException exception) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
}
}
코드 설명
@Controller로 선언된 클래스 안에서 @ExceptionHandler 어노테이션으로 메서드 안에서 발생할 수 있는 에러를 처리할 수 있다.@ExceptionHandler에 의해 발생한 예외는 ExceptionHandlerExceptionResolver에 의해 처리가 된다.@ExceptionHandler는 Exception 클래스들을 속성으로 받아 처리할 예외를 지정할 수 있다. 만약 @ExceptionHandler에 예외 클래스를 지정하지 않는다면, 파라미터에 설정된 에러 클래스를 처리하게 된다.장점
ExceptionHandler는 @ResponseStatus와 달리 에러 응답(payload)을 자유롭게 다룰 수 있다는 점에서 유연하다. 예를 들어 응답을 다음과 같이 정의해서 내려준다면 좋을 것이다.code: 어떠한 종류의 에러가 발생하는지에 대한 에러 코드message: 왜 에러가 발생했는지에 대한 설명errors: 어느 값이 잘못되어 @Valid에 의한 검증이 실패한 것인지를 위한 에러 목록파라미터와 반환 타입
ExceptionHandler의 파라미터로 HttpServletRequest나 WebRequest 등을 얻을 수 있으며 반환 타입으로는 ResponseEntity, String, void 등 자유롭게 활용할 수 있다.
더 자세한 내용은 아래 공식 문서에서 확인 가능하다.
- 파라미터: 스프링 공식 문서-Method Arguments
- 반환 타입: 스프링 공식 문서-Return Values
주의할 점
@ExceptionHandler를 사용 시에 주의할 점은 @ExceptionHandler에 등록된 예외 클래스와 파라미터로 받는 예외 클래스가 동일해야 한다는 것이다. 만약 값이 다르다면 스프링은 컴파일 시점에 에러를 내지 않다가 런타임 시점에 다음과 같은 에러를 발생시킨다.java.lang.IllegalStateException: No suitable resolver for argument [0] [type=...]
HandlerMethod details: ...
@ExceptionHandler는 컨트롤러에 구현하므로 특정 컨트롤러에서만 발생하는 예외만 처리된다. 하지만 컨트롤러에 에러 처리 코드가 섞이며, 에러 처리 코드가 중복될 가능성이 높다. 그래서 스프링은 전역적으로 예외를 처리할 수 있는 좋은 기술을 제공해준다.
ControllerAdvice는 여러 컨트롤러에 대해 전역적으로 ExceptionHandler를 적용해준다. 위에서 보이듯 ControllerAdvice 어노테이션에는 @Component 어노테이션이 있어서 ControllerAdvice가 선언된 클래스는 스프링 빈으로 등록된다.
그러므로 다음과 같이 전역적으로 에러를 핸들링하는 클래스를 만들어 어노테이션을 붙여주면 에러 처리를 위임할 수 있다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoSuchElementFoundException.class)
protected ResponseEntity<?> handleNoSuchElementFoundException(NoSuchElementFoundException e) {
final ErrorResponse errorResponse = ErrorResponse.builder()
.code("Item Not Found")
.message(e.getMessage()).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
}
@ControllerAdvice는 @Controller와 handler에서 발생하는 에러들을 모두 잡아준다.@ControllerAdvice안에서 @ExceptionHandler를 사용하여 에러를 잡을 수 있다.@ControllerAdvice를 이용하면 다음과 같은 이점을 누릴 수 있다.
일관된 예외 응답을 위해서는 다음의 사항을 주의해야 한다.
다음과 같은 예외 처리기들은 스프링의 빈으로 등록되어 있고, 예외가 발생하면 순차적으로 다음의 Resolver들이 처리가능한지 판별한 후에 예외가 처리된다.
ExceptionHandlerExceptionResolver 동작
@ExceptionHandler가 있는지 검사한다.@ExceptionHandler에서 처리가능하다면 처리하고, 그렇지 않으면 ControllerAdvice로 넘어간다.ControllerAdvice안에 적합한 @ExceptionHandler가 있는지 검사하고 없으면 다음 처리기로 넘어간다.ResponseStatusExceptionResolver 동작
@ResponseStatus가 있는지 또는 ResponseStatusException인지 검사한다.ServletResponse의 sendError()로 예외를 서블릿까지 전달되고, 서블릿이 BasicErrorController로 요청을 전달한다.DefaultHandlerExceptionResolver 동작
적합한 ExceptionResolver가 없으므로 예외가 서블릿까지 전달되고, 서블릿은 SpringBoot가 진행한 자동 설정에 맞게 BasicErrorController로 요청을 다시 전달한다.