0. 시작하게 된 계기 및 다짐😮
이번 코드스테이츠의 백엔드 엔지니어링 개발자 부트캠프
에 참여하게 되면서 현직개발자 분들의 빠른 성장을 위한 조언 중 자신만의 블로그를 이용하여 배운 것 들을 정리하는게 많은 도움이 된다 하여 시작하게 되었다.
1. 학습 목표 😮
목표 | 결과 |
---|---|
API 계층과 서비스 계층에서 발생하는 예외를 처리 및 클라이언트에 예외 메시지 전달 | O |
@ExceptionHandler 애너테이션을 사용해서 예외를 처리 | O |
@RestControllerAdvice 애너테이션을 사용해서 예외 | O |
예외 발생 시, 클라이언트 쪽에 적절한 예외 메시지를 제공해 줄 수 있다. | O |
2. 정리 😮
0. @애너테이션
1. @ExceptionHandler를 이용한 Controller 레벨에서의 예외처리(MethodArgumentNotValidException)
Spring에서는 애플리케이션 문제가 발생시, 문제를 알려서 처리뿐 아니라 유효성 검증에 실패해도 예외를 던져 처리 유도
에러메시지를 구체적으로 전송해주어, 어느 곳에서 문제가 발생한지 이해가 쉬움
[예제Code]
@ExceptionHandler
public ResponseEntity handleException(MethodArgumentNotValidException e) {
// (1)
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
// (2)
return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST);
}
(1) MethodArgumentNotValidException : 메서드 에서의 유효성 검증 실패
(2) getBindingResult().getFieldErrors() : 위의 객체에서 발생한 에러의 정보를 확인
=> 이 FieldError를 엔티티에 담아서 요청 바디에 전달
2. ErrorResponse 클래스를 생성하여 위의 fieldError정보를 담고 필요한 부분만을 클라이언트쪽에 전달
[예제 Code]
@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;
}
}
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);
3. @ExceptionHandler의 단점
1). 각 Controller 클래스 내부에 @ExceptionHanlder 메서드와 같은 코드 중복이 발생
2). @유효성검증 실패 외의 각 예외를 처리하기 위한 @ExceptionHandler 핸들러 메서드를 만들어 줘야함
[Ex. MethodArgumentNotValidException(메서드 유효성 검증실패), ConstraintViolationException(제약조건)]
[Extra]
1). @ExceptionHandler
- https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler
2). 메서드 매개변수 유효성검증(MethodArgumentNotValidException) 의 BindingResult 클래스
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/BindingResult.html
3). ContraintViolationException의 에러정보를 담고있는 ContraintVilation인터페이스와 구현 클래스
- https://docs.oracle.com/javaee/7/api/javax/validation/ConstraintViolation.html
- https://docs.jboss.org/hibernate/validator/5.3/api/org/hibernate/validator/internal/engine/ConstraintViolationImpl.html
0. @애너테이션
1. @RestControllerAdvie를 사용한 예외 처리 공통화
[예제 Code]
@RestControllerAdvice
public class GlobalExceptionAdvice {
// (1)
@ExceptionHandler
public ResponseEntity handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<ErrorResponse.FieldError> errors = .......
return new ResponseEntity<>(new ErrorResponse(errors), HttpStatus.BAD_REQUEST);
}
// (2)
@ExceptionHandler
public ResponseEntity handleConstraintViolationException(
ConstraintViolationException e) {
// TODO should implement for validation
......
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
2. ConstraintViolationException 처리를 위한 response클래스 수정
of(메서드) : 주로, 객체 생성시 어떤 값들의(of~)객체를 생성한다는 의미로 자주 사용하는 네이밍 컨벤션이다.
예제로 코드들 분석
1). ErrorResponse 클래스 정의 , ErrorCode의 필요한 부분만을 반환하기 위한 클래스
[예제 Code]
@Getter
public class ErrorResponse {
// (1) : 메서드 유효성 검사에러(MethodArgumentNotValidException )의 에러 정보를 담는 매개변수
private List<FieldError> fieldErrors;
// (2) : URI변수 유효성에러( ConstraintViolationException)의 에러 정보를 담는 매개변수
private List<ConstraintViolationError> violationErrors;
// (3) : private 접근제어자로 생성자를 만들어, new를 통한 객체 생성 금지
private ErrorResponse(final List<FieldError> fieldErrors,
final List<ConstraintViolationError> violationErrors) {
this.fieldErrors = fieldErrors;
this.violationErrors = violationErrors;
}
// (4) : [MethodArgumentNotValidException]의 에러정보를 [BindingResult]객체로 받아 ErrorResponse 객체 생성
public static ErrorResponse of(BindingResult bindingResult) {
return new ErrorResponse(FieldError.of(bindingResult), null);
}
// (5) : [Set<ConstraintViolation<?>>] 객체에 대한 ErrorResponse 객체 생성
public static ErrorResponse of(Set<ConstraintViolation<?>> violations) {
return new ErrorResponse(null, ConstraintViolationError.of(violations));
}
// (6) 메서드 유효성 검증의 에러 정보 생성
@Getter
public static class FieldError {
private String field;
private Object rejectedValue;
private String reason;
private FieldError(String field, Object rejectedValue, String reason) {
this.field = field;
this.rejectedValue = rejectedValue;
this.reason = reason;
}
public static List<FieldError> of(BindingResult bindingResult) {
final List<org.springframework.validation.FieldError> fieldErrors =
bindingResult.getFieldErrors();
return fieldErrors.stream()
.map(error -> new FieldError(
error.getField(),
error.getRejectedValue() == null ?
"" : error.getRejectedValue().toString(),
error.getDefaultMessage()))
.collect(Collectors.toList());
}
}
// (7) : ConstraintViolation Error 정보 생성
@Getter
public static class ConstraintViolationError {
private String propertyPath;
private Object rejectedValue;
private String reason;
private ConstraintViolationError(String propertyPath, Object rejectedValue,
String reason) {
this.propertyPath = propertyPath;
this.rejectedValue = rejectedValue;
this.reason = reason;
}
public static List<ConstraintViolationError> of(
Set<ConstraintViolation<?>> constraintViolations) {
return constraintViolations.stream()
.map(constraintViolation -> new ConstraintViolationError(
constraintViolation.getPropertyPath().toString(),
constraintViolation.getInvalidValue().toString(),
constraintViolation.getMessage()
)).collect(Collectors.toList());
}
}
}
2). @RestControllerAdvice를 통해 생성한 클래스로, Controller에서 발생한 에러들을 공통화하여 처리할 수 있는 클래스
[예제 Code]
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
final ErrorResponse response = ErrorResponse.of(e.getBindingResult());
return response;
}
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleConstraintViolationException(
ConstraintViolationException e) {
final ErrorResponse response = ErrorResponse.of(e.getConstraintViolations());
return response;
}
}
[Extra]
1). Controller Advice
- https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-controller-advice
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RestControllerAdvice.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html
3. 피드백 😮
API계층에서의 Controller 레벨에서의 예외가 발생할 시, 처리하는 예외처리를 학습하였다. 기본적으로 @ExceptionHandler를 이용하여 에러를 처리할 핸들러메서드를 정의해주고 ErrorResponse클래스를 따로 정의하여 이 에러를 받아 원하는 정보 만을 얻을 수 있다.
@RestControllerAdvice를 이용하여 각 클래스에 예외처리를 해줘야하는 것을 이 애너테이션이 붙은 클래스로 모든 클래스의 예외를 처리해 줄 수 있다.
@RestControllerAdvice가 붙은 클래스 안에 각 에러에 따른 예외처리를 각 Handler메서드를 정의해주고 이에 ErrorResponse 클래스의 객체를 이용하여 필요한 정보만을 받는다.
ErrorResponse클래스를 정의해주지 않고 따로 정의해 줄 수 있지만, 이를 사용해야 코드의 중복을 막고 코드를 간결하게 하고, 에러메시지를 원하는대로 컨트롤할 수 있다.
4. 앞으로 해야 될 것 😮