Spring MVC에서의 예외처리(1) -@ExceptionHandler

Backend kwon·2023년 8월 18일
0

DTO 유효성 검증에서 클라이언트 요청 데이터의 유효성 검증에 실패할 경우
받는 Response Body의 내용만으로는 요청 데이터 중에서 어떤 항목이 유효성 검증에 실패했는지 알 수가 없습니다.

클라이언트 쪽에서 에러메시지를 조금 더 구체적으로 친절하게 알 수 있도록 바꾸는 작업이 필요합니다.

 

@ExceptionHandler를 이용한 Controller 레벨에서의 예외 처리

Spring에서의 예외는 애플리케이션에 문제가 발생할 경우, 이 문제를 알려서 처리하는 것뿐만 아니라 유효성 검증에 실패했을 때와 같이 이 실패를 하나의 예외로 간주하여 이 예외를 던져서(throw) 예외 처리를 유도합니다.

MemberController에 @ExceptionHandler 적용

@RestController
@RequestMapping("/v6/members")
@Validated
@Slf4j
public class MemberControllerV6 {
    ...
		...

    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
        Member member = mapper.memberPostDtoToMember(memberDto);

        Member response = memberService.createMember(member);

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

		...
		...

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

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

위 코드를 설명하면

  1. RequestBody에 유효하지 않은 요청 데이터가 포함되어 있어 유효성 검증에 실패하고, MethodArgumentNotValidException이 발생

  2. MemberController에는 @ExceptionHandler 애너테이션이 추가된 예외 처리 메서드인 handleException()이 있기 때문에 유효성 검증 과정에서 내부적으로 던져진 MethodArgumentNotValidException을 handleException() 메서드가 전달받음.

  3. MethodArgumentNotValidException 객체에서 getBindingResult().getFieldErrors()를 통해 발생한 에러 정보를 확인

  4. 얻은 에러 정보를 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"
    }
]

그런데 클라이언트 입장에서는 의미를 알 수 없는 정보를 전부 포함한 Response Body 전체 정보를 굳이 다 전달받을 필요는 없어 보입니다.

-> 에러 정보를 기반으로 한 Error Response 클래스를 만들어서 필요한 정보만 담은 후에 클라이언트 쪽에 전달해 주면 됩니다.

 

ErrorResponse 클래스 적용

@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;
    }
}

ErrorResponse를 사용하도록 MemberController의 handleException() 메서드 수정

...

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

        
        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);
    }
}

필요한 정보들만 선택적으로 골라서 ErrorResponse.FieldError 클래스에 담아서 List로 변환 후, List<ErrorResponse.FieldError>를 ResponseEntity 클래스에 실어서 전달하고 있습니다.

 

@ExceptionHandler의 단점

  1. 각각의 Controller 클래스에서 @ExceptionHandler 애너테이션을 사용하여 Request Body에 대한 유효성 검증 실패에 대한 에러 처리를 해야 되므로 각 Controller 클래스마다 코드 중복이 발생합니다.

  2. Controller에서 처리해야 되는 예외가 유효성 검증 실패에 대한 예외(MethodArgumentNotValidException)만 있는 것이 아니기 때문에 하나의 Controller 클래스 내에서 @ExceptionHandler를 추가한 에러 처리 핸들러 메서드가 늘어납니다.

profile
백엔드개발자를 향해서

0개의 댓글