Spring은 전역적으로 ExceptionHandler를 적용할 수 있는 @ControllerAdvice 와 @RestControllerAdvice annotation을 제공
@ControllerAdivce는 여러 컨트롤러에 대해 전역적으로 ExceptionHandler를 적용
에러를 핸들링하는 클래스를 만들어 어노테이션을 붙여주면 에러 처리를 위임하게 됨
만약 특정 클래스에만 제한적으로 적용하고 싶다면, @RestControllerAdvice의 basePackages 등을 설정함으로써 제한이 가능
@RestControllerAdvice는 @ControllerAdvice와 달리 @ResponseBody가 붙어 있어 응답을 Json으로 반환
하나의 클래스로 모든 컨트롤러에 대해 전역적으로 예외 처리가 가능
직접 정의한 에러 응답을 일관성 있게 클라이언트에게 반환 가능
별도의 try-catch문이 없어 코드의 가독성이 높아짐
한 프로젝트당 하나의 ControllerAdivce만 관리하는 것이 좋음
만약 여러 ControllerAdvice가 필요하다면 basePackages나 annotations 등을 지정해야 함
직접 구현한 Exception 클래스들은 한 공간에서 관리
Spring은 스프링 예외를 미리 처리해둔 ResponseEntityExceptionHandler를 추상 클래스로 제공
ResponseEntityExceptionHandler에는 스프링 예외에 대한 ExceptionHandler가 모두 구현되어 있으므로 @ControllerAdvice 클래스가 이를 상속해서 사용하면 됨
이 추상 클래스를 상속받지 않는다면 스프링 예외들은 DefaultHandlerExceptionResolver가 처리하게 되는데, 그러면 예외 처리기가 달라지므로 클라이언트가 일관되지 못한 에러 응답을 받지 못하므로 ResponseEntityExceptionHandler를 상속시키는 것이 좋음
@RestControllerAdvice
public class ExceptionAdvice extends ResponseEntityExceptionHandler {
// NotFoundAccountException 발생시 에러 처리
@ExceptionHandler(NotFoundAccountException.class)
public ResponseEntity<?> handleNotFoundEntity(NotFoundException e) {
return handleExceptionInternal(e.getExceptionCode());
}
}
ResponseEntityExceptionHandler의 handleExceptionInternal() 메소드를 오버라이딩하여 응답을 customizing 가능
@Slf4j
@RestControllerAdvice(annotations = {RestController.class})
public class ExceptionAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = {GlobalException.class})
protected ResponseEntity<ErrorResponse> handleGlobalException(GlobalException e) {
log.error("{}: {}", e.getGlobalErrorCode(), e.getMessage());
return handleExceptionInternal(e.getGlobalErrorCode());
}
private ResponseEntity<ErrorResponse> handleExceptionInternal(GlobalErrorCode globalErrorCode) {
return ResponseEntity.status(globalErrorCode.getHttpStatus().value())
.body(new ErrorResponse(globalErrorCode));
}
}
@Getter
public class ErrorResponse {
private final LocalDateTime timestamp = LocalDateTime.now();
private final Boolean isSuccess;
private final String code;
private final String message;
public ErrorResponse(GlobalErrorCode globalErrorCode) {
this.isSuccess = false;
this.code = globalErrorCode.getCode();
this.message = globalErrorCode.getMessage();
}
}