이번 포스트에서는 Spring Boot 에서 예외처리를 하는 방법에 대해서 알아보겠습니다.
Github에서 샘플 프로젝트의 전체 코드를 확인할 수 있습니다.
GitHub - xvzc/spring-test: spring test repo
Controller Advice
아래 코드는 서비스 레이어에서 발생하는 모든 예외를 처리하는 GlobalExceptionHandler
입니다. 개발자가 처리하지 못한 예외가 발생하는 경우, Exception 객체의 핸들러가 동작합니다. 이상적인 ****목표는 가능한 모든 예외를 핸들링 하여 internal server error
를 응답하지 않는 것입니다.
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
| @Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/** Custom Exceptions START */
// 자원을 찾을 수 없는 경우
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ResourceNotFoundException.class)
protected ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) {
log.error("handleMethodArgumentNotValidException", e);
return ErrorResponse.of(BasicError.of(e.errorCode));
}
// Unique
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(AlreadyExistsException.class)
protected ErrorResponse handleAlreadyExistsException(AlreadyExistsException e) {
log.error("handleAlreadyExistsException", e);
return ErrorResponse.of(BasicError.of(e.errorCode));
}
/** Custom Exceptions END*/
/** Built in Exceptions START */
// Bean Validation 실패
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
...
}
// Bean Validation 실패
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(DataIntegrityViolationException.class)
protected ErrorResponse handleDataIntegrityViolationException(DataIntegrityViolationException e) {
...
}
...
// 나머지 모든 예외들
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
protected ErrorResponse handleException(Exception e) {
log.error("handleEntityNotFoundException", e);
return ErrorResponse.of(BasicError.of(ErrorCode.INTERNAL_SERVER_ERROR));
}
/** Built in Exceptions END */
}
|
ErrorCode
ErrorCode
클래스는 에러 메시지와 코드를 정의합니다.
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
| @JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ErrorCode {
// GLOBAL
INTERNAL_SERVER_ERROR(500, "G-000", "Server Error"),
INVALID_INPUT_VALUE(400, "G-001", " Invalid Input Value"),
METHOD_NOT_ALLOWED(405, "G-002", " Method not allowed"),
INVALID_TYPE_VALUE(400, "G-003", " Invalid Type Value"),
ACCESS_DENIED(403, "G-004", "Access is Denied"),
// User
EMAIL_DUPLICATION(400, "USER-001", "Email already exists"),
LOGIN_FAILED(400, "USER-002", "Login failed"),
USER_NOT_FOUND(404, "USER-003", "User not found"),
USER_DUPLICATION(400, "USER-004", "User Already Exists")
;
private final String code;
private final String message;
private int status;
ErrorCode(final int status, final String code, final String message) {
this.status = status;
this.message = message;
this.code = code;
}
public String getMessage() {
return this.message;
}
public String getCode() {
return code;
}
public int getStatus() {
return status;
}
}
|
이런 방식을 사용하게 되면 UserNotFoundException
, GrabNotFoundException
등을 다음과 같이 처리할 수 있습니다.
1
2
3
4
5
6
7
| User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException(ErrorCode.USER_NOT_FOUND));
// OR
Grab grab = grabRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException(ErrorCode.GRAB_NOT_FOUND));
|
ErrorResponse
효율적인 에러 처리를 위해 예외 응답을 통일합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| @Getter
public class ErrorResponse {
Object data = null;
BasicError error;
private ErrorResponse(BasicError error) {
this.data = null;
this.error = error;
}
public static ErrorResponse of(BasicError error) {
return new ErrorResponse(error);
}
}
|
에러 객체
아래는 에러 객체의 정의입니다.
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
| @Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class BasicError {
private String message;
private int status;
private List<FieldError> fields;
private String code;
private BasicError(final ErrorCode code, final List<FieldError> fields) {
this.message = code.getMessage();
this.status = code.getStatus();
this.fields = fields;
this.code = code.getCode();
}
private BasicError(final ErrorCode code) {
this.message = code.getMessage();
this.status = code.getStatus();
this.code = code.getCode();
this.fields = new ArrayList<>();
}
public static BasicError of(final ErrorCode code, final BindingResult bindingResult) {
return new BasicError(code, FieldError.of(bindingResult));
}
public static BasicError of(final ErrorCode code) {
return new BasicError(code);
}
...
}
|