Coding/Back - Spring Framework

Spring 예외처리 #Day7

꿀딴지- 2023. 8. 24. 17:47

참고. JAVA 예외처리

 

유효성검증(@Valid, @Validated)으로 실패응답("status": 400, "error": "Bad Request" / "status": 500, "error": "Internal Server Error")을 줄 수 있지만, 더 상세하게 혹은 비즈니스 커스텀한 예외가 필요

→ Spring에서는 에너테이션을 적극 활용한 예외처리 지원

 

1. @ExceptionHander : Controller 레벨에서의 예외처리

step1. Controller 클래스 내에 예외처리 메서드 구현

  • reqbody에 유효성검증(@Valid) 실패 시 예외처리메서드(@ExceptionHandelr) 호출
  • e.getBindingResult().getFieldErrors() 를 통해 전달되는 메시지가 상세하기 때문에 필요한 정보로만 커스텀 필요
//public class MemberController
//public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {...}
@ExceptionHandler
public ResponseEntity handleException(MethodArgumentNotValidException e){
    final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
    return new ResponseEntity(fieldErrors, HttpStatus.BAD_REQUEST);
}

step2. 필요한 Error 정보만 담을 수 있는 Error 전용 Response 객체 정의

  • FieldError 클래스를 새로 정의해서 res return 시 새로 정의한 객체 반환
    • FieldError 클래스 : ErrorResponse 클래스 내부에 정의되어있지만 내부 클래스 보다는 static 멤버 클래스라고 부르는게 적절
@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;
    
}

//-------------
//public class MemberController
//public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {...}
@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);
}

⇒ 각 Controller 클래스마다 동일한 예외처리 필요할 수 있음

 

2. @RestControllerAdvice 예외처리 공통화

  • 예외를 전역적으로 관리 : 중복으로 예외처리가 필요없어지고, 예외가 발생하면 throws(try-catch)가 없어도 전역 예외관리인 @RestControllerAdvice 클래스 내에서 해당하는 예외가 있는지 확인해서 처리함
  • 1개만 만드는걸 권장. 여러개가 되면 우선순위 등의 세부 설정이 필요함(복잡도 증가)

step1. 예외를 전역으로 관리할 클래스를 만들고 @RestControllerAdvice 추가

step2. 각 예외처리를 해야 할 메서드는 @ExceptonHandler 추가

옵션. @ResponseStatus(HttpStatus.BAD_REQUEST) http status를 http response에 포함

// 전역 예외처리 클래스
@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.NOT_FOUND) ResponseEntity에 httpStatus 넣어도 됨
    public ResponseEntity handleBusinessLogicException(BusinessLogicException e) {
        final  ErrorResponse response = ErrorResponse.of(e.getExceptionCode());
        return new ResponseEntity<>(response, HttpStatus.valueOf(e.getExceptionCode().getStatus()));
    }

비즈니스적인 예외 던지기(throw) 및 예외처리

  • throw 키워드를 사용해 예외를 메서드 바깥으로 던짐 → 메서드를 호출한 지점으로 던져짐
    → 즉, 서비스 계층에서 예외를 던지면, Controller 핸들러 메서드에서 받음
  • Controller에서 발생하는 예외는 Exception Advice에서 공통으로 처리하고 있으므로, 서비스 계층에서 받은 예외도 함께 처리할 수 있음
  • throw new RuntimeException("Not found member");

 

참고1. 예외처리 종류

  • MethodArgumentNotValidException : RequestBody(@Vaild) 유효성 검증
  • ConstraintViolationException : PathURI(@Validated) 유효성 검증

 

참고2. @RestControllerAdvice(spring mvc 4.3이후) vs @ControllerAdvice

  • @RestControllerAdvice = @ControllerAdvice + @ResponseBody
  • @ResponseBody 를 포함하고 있기 때문에 JSON 형식의 데이터를 Response Body로 전송하기 위해서 ResponseEntity로 데이터를 래핑 할 필요가 없다