나는 항상 @ControllerAdvice
를 통해 전역으로 예외처리를 진행하였다.
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 클라이언트의 잘못된 값 전달
* @param e
* @return
*/
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequestException(BadRequestException e){
log.error("handleMethodArgumentNotValidException", e);
ErrorResponse response = ErrorResponse.of(e.getErrorCode(),e.getFieldErrors());
return new ResponseEntity<>(response,HttpStatus.BAD_REQUEST);
}
/**
* @Validated 로 binding error 발생시 발생
* HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할 경우 발생
* 주로 @RequestBody 나 @RequestPart 어노테이션에서 발생
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
log.error("handleMethodArgumentNotValidException", e);
ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
/**
* @ModelAttribut 으로 binding error 발생시 발생
*/
@ExceptionHandler(BindException.class)
protected ResponseEntity<ErrorResponse> handleBindException(BindException e){
log.error("handleBindException", e);
ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
/**
* enum type 일치하지 않아 binding 못할 경우 발생
* 주로 @RequestParam enum 으로 binding 못했을 경우 발생
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e){
log.error("handleMethodArgumentTypeMismatchException",e);
ErrorResponse response = ErrorResponse.of(e);
return new ResponseEntity<>(response,HttpStatus.BAD_REQUEST);
}
대충 이런 식이다. 그런데, 항상 이렇게 공통적으로 발생하는 녀석들에 대해 예외처리를 하는 과정이 반복되고 굉장히 번거로웠다. 특히 뭘 또 놓친것이 아닌지 걱정이 되곤 했다.
그러다가 스프링에 굉장히 유용한 유틸성 객체가 존재하는 것을 알게 되었다.(나도 너무 귀찮아서 이와 비슷한 걸 미리 정의해 놓을까 고민 중이었는데, 역시.. 스프링 모든게 있다.)
바로, ResponseEntityExceptionHandler
이다.
public abstract class ResponseEntityExceptionHandler implements MessageSourceAware {
추상 클래스로, 우리가 할 것은 protected로 정의된 handle***
메서드만 오버라이드 하여 정의하면 된다.
이것이 무엇인지 내부에 정의된 메서드를 하나 보자
@ExceptionHandler({
MethodNotAllowedException.class,
NotAcceptableStatusException.class,
UnsupportedMediaTypeStatusException.class,
MissingRequestValueException.class,
UnsatisfiedRequestParameterException.class,
WebExchangeBindException.class,
ServerWebInputException.class,
ServerErrorException.class,
ResponseStatusException.class,
ErrorResponseException.class
})
public final Mono<ResponseEntity<Object>> handleException(Exception ex, ServerWebExchange exchange) {
if (ex instanceof MethodNotAllowedException theEx) {
return handleMethodNotAllowedException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof NotAcceptableStatusException theEx) {
return handleNotAcceptableStatusException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof UnsupportedMediaTypeStatusException theEx) {
return handleUnsupportedMediaTypeStatusException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof MissingRequestValueException theEx) {
return handleMissingRequestValueException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof UnsatisfiedRequestParameterException theEx) {
return handleUnsatisfiedRequestParameterException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof WebExchangeBindException theEx) {
return handleWebExchangeBindException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof ServerWebInputException theEx) {
return handleServerWebInputException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof ServerErrorException theEx) {
return handleServerErrorException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof ResponseStatusException theEx) {
return handleResponseStatusException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else if (ex instanceof ErrorResponseException theEx) {
return handleErrorResponseException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
else {
if (logger.isWarnEnabled()) {
logger.warn("Unexpected exception type: " + ex.getClass().getName());
}
return Mono.error(ex);
}
}
참고로 나는 지금 webflux로 작업중이기 때문에 이렇게 떳지만, mvc에도 동일한 클래스가 있으며, 리턴값만 차이난다.
그렇다. 웹 프레임워크가 던지는 예외를 미리 다 정의해 놓았다.(너무 편리하다.)
우리는 저기에 있는 protected로 선언되어있는 handle***
메서드만 오버라이드 하면 된다.