{
"timestamp" : "2022-06-28T15:28:32.132+00:00",
"status" : 400,
"error" : "Bad Request",
"path" : "v1/members"
}
@ExceptionHandler
@RestController
@RequestMapping("/v6/members")
@Validated
@Slf4j
public class MemberController {
...
...
@ExceptionHandler
// 인자로 받는 예외가 발생할 때, 아래 코드를 실행
public ResponseEntity handleException(MethodArgumentNotValidException e) {
// e.getBindingResult().getFieldErrors() 에 에러 내용을 담고 있음
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
// ResponseEntity를 통해 return, 관련 정보를 모두 담고 있음
return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler
public ResponseEntity handleConstraintViolationException(
ConstraintViolationException e) {
final List<ConstraintViolation> constraintViolations = e.getConstraintViolations();
return new ResponseEntity<>(constraintViolations, HttpStatus.BAD_REQUEST);
}
}
@RestControllerAdvice
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler
public ResponseEntity handleMethodArgumentNotValidException(
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);
}
현 상황의 문제점
ErrorResponse Object
@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;
}
// BindingResult에 대한 ErrorResponse 객체 생성
public static ErrorResponse of(BindingResult bindingResult) {
return new ErrorResponse(FieldError.of(bindingResult), null);
}
// Set<ConstraintViolation<?>> 객체에 대한 ErrorResponse 객체 생성
public static ErrorResponse of(Set<ConstraintViolation<?>> violations) {
return new ErrorResponse(null, ConstraintViolationError.of(violations));
}
//Field Error 가공
@Getter
@AllArgsConstructor
public static class FieldError {
private String field;
private Object rejectedValue;
private String 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());
}
}
// ConstraintViolation Error 가공
@Getter
@AllArgsConstructor
public static class ConstraintViolationError {
private String propertyPath;
private Object rejectedValue;
private String 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());
}
}
}
@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) : 발생한 예외를 잡아서(catch) 체크한 후에 어떤 구체적인 처리를 해야 하는 예외 (try, catch 문으로 처리해주어야 함)
언체크 예외(Unchecked Exception):예외를 잡아서(catch) 해당 예외에 대한 어떤 처리를 할 필요가 없는 예외
RuntimeException을 상속하여 직접 언체크 예외를 만들 수 있다.
개발자가 의도적으로 예외를 던지는 상황
사용자 정의 예외(Custom Exception)
public enum ExceptionCode {
MEMBER_NOT_FOUND(404, "Member Not Found");
@Getter
private int status;
@Getter
private String message;
ExceptionCode(int status, String message) {
this.status = status;
this.message = message;
}
}
public class BusinessLogicException extends RuntimeException {
@Getter
private ExceptionCode exceptionCode;
public BusinessLogicException(ExceptionCode exceptionCode) {
super(exceptionCode.getMessage());
this.exceptionCode = exceptionCode;
}
}
// 실사용 예시
public Member findMember(long memberId) {
...
throw new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND);
}
사용자 정의 예외 처리
@RestControllerAdvice
public class GlobalExceptionAdvice {
...
...
@ExceptionHandler
public ResponseEntity handleBusinessLogicException(BusinessLogicException e) {
// ErrorResponse 도 형태를 맞추어 일부 추가해주어야 함
final ErrorResponse response = ErrorResponse.of(e.getExceptionCode());
HttpStatus httpStatus = HttpStatus.valueOf(e.getExceptionCode().getStatus())
// 처리마다 에러코드가 다르다면, ResponseEntity를 사용하는 것이 용이
return new ResponseEntity<>(response, statusCode);
}
}