본문 바로가기

STUDY/Spring

Spring Boot | @ControllerAdvice로 Exception 처리하기

참고!

 

Spring Guide - Exception 전략 - Yun Blog | 기술 블로그

Spring Guide - Exception 전략 - Yun Blog | 기술 블로그

cheese10yun.github.io


0. Controller

/messages URI로 POST요청을 하면, @Valid 어노테이션이 설정되어 있기 때문에 MesseageParam에 설정된 검증 항목(?)들을 검사한다.

만약 검증을 통과하지 못하면..! (더 큰 값을 보낸다거나 필수값을 보내지 않는 등..) Exception이 발생한다..

@RestController
public class MessageController {

    @PostMapping(value = "/messages", produces = "application/json")
    public ResponseEntity<?> sendSMS(@Valid @RequestBody MessageParam param) {

        return new ResponseEntity<>(HttpStatus.OK);
    }

}

 

1. GlobalExceptionHandler작성

이 클래스에 @RestContollerAdvice어노테이션을 설정함으로써 컨트롤러에서 발생하는 Exception들을 처리할 수 있다..!

RestControllerAdvice는 ControllerAdvice + ResponseBody

@ControllerAdvice는 기본적으로 모든 컨트롤러(@Controller)를 감지해 처리하고, basePackage()와 같은 선택자를 이용해 특정 컨트롤러에만 작동하도록 할 수도 있다.

우선 위의 컨트롤러에 적어둔대로 @Valid 유효성 검사를 통과하지 못했을 때 발생하는 MethodArgumentNotValidException을 

@ExceptionHandler에 등록(?)하고 해당 Exception이 발생하면 ControllerAdvice가 감지한다..

 

+) ResponseBody로 리턴할 ErrorResponse 객체를 만들어 표준화하면 좋은데, 그 방법은 맨 위의 링크를 참고하면 된다.

@RestControllerAdvice   // 컨트롤러에서 발생한 Exception들을 모두 이 곳에서 처리할 수 있도록
@Slf4j
public class GlobalExceptionHandler {

    /* @Valid 로 유효성 검사했을 때 발생한 에러 */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException", e);
        
        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }
    
}
 

ControllerAdvice (Spring Framework 5.3.5 API)

Specialization of @Component for classes that declare @ExceptionHandler, @InitBinder, or @ModelAttribute methods to be shared across multiple @Controller classes. Classes annotated with @ControllerAdvice can be declared explicitly as Spring beans or auto-d

docs.spring.io

 

2. 테스트!

call_back이라는 field는 현재 @NotBlank로 설정되어있어서 Null과 빈문자열("") 그리고 공백(" ")을 허용하지 않는다.

call_back에 아무런 문자열도 입력하지 않고 요청했을 때 저렇게 아주 친절하게 알려준다...

 

+) 직접 만든 제약 (Custom Constraint)에 대한 ExceptionHandling

MethodArgumentNotValidException은 BindingResult를 이용해 어떤 항목들이 유효성 검사를 통과하지 못했는지 알 수 있고,

ConstraintViolationException은 ConstraintViolations를 이용해 알 수 있다..!

/* Custom Constraint 관련 에러 */
@ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException e) {
  log.error("ConstraintViolationException", e);
  final ErrorResponse response =
  	ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getConstraintViolations().iterator());
  return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}

ErrorResponse 👇

더보기
/* 생략 ... */
public static ErrorResponse of(final ErrorCode code, final Iterator<ConstraintViolation<?>> violationIterator) {
	return new ErrorResponse(code, FieldError.of(violationIterator));
}

/* 생략 ... */
private static List<FieldError> of(final Iterator<ConstraintViolation<?>> violationIterator) {
  List<FieldError> fieldErrors = new ArrayList<>();
  while (violationIterator.hasNext()) {
    final ConstraintViolation<?> constraintViolation = violationIterator.next();
    fieldErrors.add(new FieldError(
    	getFieldName(constraintViolation.getPropertyPath().toString()),
    	constraintViolation.getInvalidValue() == null? "" : constraintViolation.getInvalidValue().toString(),
    	constraintViolation.getMessage()
    ));
  }
  return fieldErrors;
}

/* 생략 ... */