예외처리 필요성
1
2
3
4
5
6
{
"timestamp":"2022-09-12T17:33:42.132+00:00",
"status": 400,
"error": "Bad Request",
"path": "/v1/members"
}
- 예외처리 없이 유효성 검사 실패 시 응답 메세지
- 클라이언트는 위 Response Body의 내용만으로는 요청 데이터 중 어떤 항목이 유효성 검증에 실패했는지 알수없기에 에러메세지를 더 구체적으로 알 수 있도록 바꾸는 작업 필요
Spring MVC 예외처리
@ExceptionHandler 예외처리
- 한 Controller 클래스 내에서 발생하는 예외 처리
- Controller마다 동일하게 발생하는 예외 처리에 대한 중복 코드가 발생
- 다양한 유형의 예외 처리에는 한계 존재
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
@RestController @RequestMapping("/v6/members") @Validated @Slf4j public class MemberController { ... ... @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) { // getBindingResult().getFieldErrors()를 통해 발생한 에러 정보를 확인 final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); // 위에서 얻은 정보를 ResponseEntity를 통해 Response Body로 전달 return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST); } }
ErrorResponse Object
- 따로 객체를 만들어 필드에 지정된 특정 값들로만 응답
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
@Getter public class ErrorResponse { private List<FieldError> fieldErrors; private List<ConstraintViolationError> violationErrors; private ErrorResponse(final List<FieldError> fieldErrors, final List<ConstraintViolationError> violationErrors) { this.fieldErrors = fieldErrors; this.violationErrors = violationErrors; } public static ErrorResponse of(BindingResult bindingResult) { return new ErrorResponse(FieldError.of(bindingResult), null); } public static ErrorResponse of(Set<ConstraintViolation<?>> violations) { return new ErrorResponse(null, ConstraintViolationError.of(violations)); } @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()); } } @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()); } } }
1 2 3 4 5 6 7 8 9 10 11
@RestControllerAdvice public class GlobalExceptionAdvice { @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleMethodArgumentNotValidException( MethodArgumentNotValidException e) { final ErrorResponse response = ErrorResponse.of(e.getBindingResult()); return response; } }
비즈니스 로직 예외 처리
Checked Exception와 Unchecked Exception
- Checked Exception
- 발생한 예외를 잡아서(catch) 체크한 후에 해당 예외를 복구 or 회피 등의 어떤 구체적인 처리 하는 예외
- 대표적 체크예외 : ClassNotFoundException
- Unchecked Exception
- 예외를 잡아서(catch) 해당 예외에 대한 어떤 처리를 할 필요가 없는 예외
- RuntimeException을 상속하여 직접 언체크 예외 가능
- 대표적 언체크 예외 : NullPointerException, ArrayIndexOutOfBoundsException
개발자가 의도적으로 예외를 던질 수(throw) 있는 상황
- 백엔드 서버와 외부 시스템과의 연동에서 발생하는 에러 처리
- 시스템 내부에서 조회하려는 리소스(자원, Resource)가 없는 경우
의도적인 예외 던지기/받기(throw/catch)
1
2
3
4
5
public Member findMember(long memberId) {
//TODO
throw new RuntimeException("Not found member");
}
- 예외 던지기(throw)
1
2
3
4
5
6
7
@ExceptionHandler
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFoundException(RuntimeException e) {
System.out.println(e.getMessage());
return null;
}
- 예외 잡기(catch)