노션으로 다시 돌아갔습니다 😅

[예외 처리] @ExceptionHandler를 이용한 예외 처리

by mignon25

1. MemberController에 @ExceptionHandler 적용

  • @ExceptionHandler 애너테이션을 이용해 예외를 처리하도록 handleException() 메서드 추가
@RestController
@RequestMapping("/v6/members") // v6: @ExceptionHandler 이용한 예외 처리
@Validated
@Slf4j
public class MemberController {

    ...
   
    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberPostDto) {

        Member member = mapper.memberPostDtoToMember(memberPostDto);

        Member response = memberService.createMember(member);

        return new ResponseEntity<>(mapper.memberToMemberResponseDto(response), HttpStatus.CREATED);
    }

	...
    ...

    @ExceptionHandler
    public ResponseEntity handleException(MethodArgumentNotValidException e) {
        // 1
        final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();

        // 2
        return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST);
    }

}

< Request Body 의 유효성 검증 실패 시 예외 처리 과정 >

  1. MemberController 에는 @ExceptionHandler 애너테이션이 추가된 상태
  2. 클라이언트에서 MemberController의 postMember() 핸들러 메서드에 요청 전송 (POST /v6/members)
  3. RequestBody 에 유효하지 않은 요청 데이터가 포함되어 있어 유효성 검증에 실패
  4. MethodArgumentNotValidException 발생
  5. 유효성 검증 과정에서 내부적으로 던져진 MethodArgumentNotValidException 을 handleException() 메서드가 전달받음
  6. (1) 과 같이 MethodArgmentNotValidException 객체(예외 객체)에서 getBindingResult().getFieldErrors() 를 통해 발생한 에러 정보 확인
  7. (1) 에서 얻은 에러 정보를 (2)에서 ResponseEntity를 통해 Response Body로 전달

 

// 아래와 같은 Request Body 로 요청
{
    "email": "asdf@email",
    "name": "ㅁㄴㅇㄹ",
    "phone": "010-1111-1111"
}
// 결과

[
    {
        "codes": [
            "EnhancedEmail.memberPostDto.email",
            "EnhancedEmail.email",
            "EnhancedEmail.java.lang.String",
            "EnhancedEmail"
        ],
        "arguments": [
            {
                "codes": [
                    "memberPostDto.email",
                    "email"
                ],
                "arguments": null,
                "defaultMessage": "email",
                "code": "email"
            }
        ],
        "defaultMessage": "올바른 형식의 이메일 주소여야 합니다.",
        "objectName": "memberPostDto",
        "field": "email",
        "rejectedValue": "asdf@email",
        "bindingFailure": false,
        "code": "EnhancedEmail"
    }
]

=> 원하는 에러 메세지는 담겨있기는 한데... 영 지저분하다.

 

 

 ErrorResponse 클래스를 적용해 깔끔하게 에러 메세지 받기

1. ErrorResponse 클래스 작성

@Getter
@AllArgsConstructor
public class ErrorResponse {
    // 1
    private List<FieldError> fieldErrors;

    // 2
    @Getter
    @AllArgsConstructor
    public static class FieldError { // 하나의 필드 에러 정보를 담을 클래스
        private String field;
        private Object rejectedValue;
        private String reason;
    }
}
  • 유효성 검증 실패 시, 실패한 필드(멤버 변수)에 대한 Error 정보만 담아서 응답으로 전송하기 위한 클래스(V1)
  • 클라이언트로 전달된 에러 메세지는 JSON 배열의 형태
    (유효성 검증에 실패한 멤버 변수가 여러 개일 수 있으므로...)
  • 따라서 List 객체 이용! => List<ErrorResponse.FieldError>

 

2. ErrorResponse 객체를 응답으로 전송 - MemberController handleException() 수정

@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<>(fieldErrors, HttpStatus.BAD_REQUEST);
}

 

 

@ExceptionHandler의 단점

  1. 각각의 Controller 클래스에서 @ExceptionHandler 애너테이션을 사용해 Request Body 에 대한 유효성 검증 실패에 대한 예외처리를 해야 한다. 
    => 각 Controller 클래스마다 코드 중복 발생

  2. Controller에서 처리해야 하는 예외(Exception)가 유효성 검증 실패에 대한 예외(MethodArgumentNotValidException)만 있는 것이 아니다.
    => @ExceptionHandler 를 추가한 에러 처리 핸들러 메서드가 하나의 Controller 클래스 내에 여러 개가 될 수 있다. 
    => 코드 복잡도 증가
    @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<>(errors, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler
    public ResponseEntity handleException(ConstraintViolationException e) {
        /**
         * - ConstraintViolationException 클래스는 getBindingResult().getFieldErrors() 
         * 와 같이 에러 정보를 얻을 수 없다.
         * - MethodArgumentNotValidException과 다르게 또 다른 방식으로 처리가 필요.
         */
        
        return new ResponseEntity(HttpStatus.BAD_REQUEST);
    }
    
    @ExceptionHandler
    ...

 

 

Additional Keywords

 

 

 

 

 

 

 

 

 

블로그의 정보

Mignon'S Dev Log

mignon25

활동하기