유효성검증(@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로 데이터를 래핑 할 필요가 없다
'Coding > Back - Spring Framework' 카테고리의 다른 글
Spring Persistence(DataAccess) Layer : JPA 개념 1/3 #Day9 (0) | 2023.08.28 |
---|---|
(review) Spring 구성하기 #Day8 (0) | 2023.08.28 |
Spring Business(Service) Layer #Day6 (0) | 2023.08.23 |
Spring DTO, Validation #Day5 (0) | 2023.08.22 |
Spring Presentation(API) Layer #Day4 (0) | 2023.08.21 |