@ExceptionHandler
public ResponseEntity handleException(MethodArgumentNotValidException e) {
// (1)
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
// (2)
return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST);
MethodArgumentNotValidException
객체에서 getBindingResult().getFieldErrors()
를 통해 발생한 에러 정보를 확인할 수 있다.
(1)에서 얻은 에러 정보를 ResponseEntity
를 통해 Response Body
로 전달한다.
[
{
"codes": [
"Email.memberPostDto.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"memberPostDto.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"arguments": null,
"defaultMessage": ".*",
"codes": [
".*"
]
}
],
"defaultMessage": "올바른 형식의 이메일 주소여야 합니다",
"objectName": "memberPostDto",
"field": "email",
"rejectedValue": "hgd@",
"bindingFailure": false,
"code": "Email"
}
]
위와 같이, 유효성 검사 실패에 대한 에러 메시지를 구체적으로 전송해주기 때문에 클라이언트 입장에서는 이제 어느 곳에 문제가 있는지를 구체적으로 알 수 있게 되었다.
하지만, 굳이 알 수 없는 모든 정보를 포함한 메세지를 받을 필요성은 없어보인다.
문제가 되는 정보만 받아볼 수 있으면 더 효율적이지 않을까?
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class ErrorResponse {
// (1)
private List<FieldError> fieldErrors;
@Getter
@AllArgsConstructor
public static class FieldError {
private String field;
private Object rejectedValue;
private String reason;
}
}
DTO 클래스의 유효성 검증 실패 시, 실패한 필드(멤버 변수)에 대한 Error 정보만 담아서 응답으로 전송하기 위한 ErrorResponse
클래스다.
(1) 과 같이 한 개 이상의 유효성 검증에 실패한 필드의 에러 정보를 담기 위해서 List 객체를 이용하며, 이 한개의 필드 에러 정보는 FieldError
라는 별도의 static class를 ErrorResponse
클래스의 멤버 클래스로 정의했다.
@ExceptionHandler
public ResponseEntity handleException(MethodArgumentNotValidException e) {
// (1)
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
// (2)
List<ErrorResponse.FieldError> errors =
fieldErrors.stream()
.map(error -> new ErrorResponse.FieldError(
error.getField(),
error.getRejectedValue(),
error.getDefaultMessage()))
.collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponse(errors), HttpStatus.BAD_REQUEST);
Controller에 위의 코드를 추가해준다.
(1) 에서는 List를 통째로 ResponseEntity 클래스에 실어서 전달했지만
(2) 에서는 필요한 정보들만 선택적으로 골라서 ErrorResponse.FieldError
클래스에 담아서 List로 변환 후, List<ErrorResponse.FieldError>
를 ResponseEntity
클래스에 실어서 전달하고 있다.
@ExceptionHandler
애너테이션으로 에러 처리를 하게되면 다음과 같은 문제점이 발생할 수 있다.
각각의 Controller 클래스에서 @ExceptionHandler
애너테이션을 사용하여 Request Body에 대한 유효성 검증 실패에 대한 에러 처리를 해야되므로 각 Controller 클래스마다 코드 중복이 발생한다.
Controller에서 처리해야 되는 예외(Exception)가 유효성 검증 실패에 대한 예외(MethodArgumentNotValidException)만 있는것이 아니기 때문에 하나의 Controller 클래스 내에서 @ExceptionHandler
를 추가한 에러 처리 핸들러 메서드가 늘어난다.
Controller 클래스 레벨에서 @ExceptionHandler
애너테이션을 사용하면 해당 Controller에서 발생하는 예외를 처리할 수 있다.
필요한 Error 정보만 담을 수 있는 Error 전용 Response 객체를 사용하면 클라이언트에게 조금 더 친절한 에러 정보를 제공할 수 있다.
@ExceptionHandler
애너테이션 방식은 Controller마다 동일하게 발생하는 예외 처리에 대한 중복 코드가 발생할 수 있다.
@ExceptionHandler
애너테이션 방식은 다양한 유형의 예외를 처리하기에는 적절하지 않은 방식이다.