커스텀 예외를 처리하는 방법 중, ControllerAdvice를 통해 예외를 처리하는 방법을 알게돼서 프로젝트에 적용해보았다.
하지만, 예상보다 만들어지는 커스텀 예외의 수는 많았고, 만들어질 때마다 제목만 다르고 내용은 거의 비슷한, 즉 중복 코드가 많아지는 현상이 발생했다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalBoardTypeException.class)
public ResponseEntity<ExceptionResponseDto> handleIllegalBoardTypeException() {
return ResponseEntity
.status(CustomException.ILLEGAL_BOARD_TYPE.getStatusCode())
.body(CustomException.ILLEGAL_BOARD_TYPE.toDto());
}
@ExceptionHandler(NotFoundBoardException.class)
public ResponseEntity<ExceptionResponseDto> handleNotFoundBoardException() {
return ResponseEntity
.status(CustomException.NOT_FOUND_BOARD.getStatusCode())
.body(CustomException.NOT_FOUND_BOARD.toDto());
}
@ExceptionHandler(NotMatchUserException.class)
public ResponseEntity<ExceptionResponseDto> handleNotMatchUserException() {
return ResponseEntity
.status(CustomException.NOT_MATCH_USER.getStatusCode())
.body(CustomException.NOT_MATCH_USER.toDto());
}
...
}
이를 해결할 수 있는 방법에 대해 알아보겠다.
대표 커스텀 예외 클래스를 만들고, 안에 내용을 상황에 맞게 설정한다.
public class CustomException extends RuntimeException {
private final ExceptionCode exceptionCode;
public CustomException(ExceptionCode exceptionCode) {
super(exceptionCode.getMessage());
this.exceptionCode = exceptionCode;
}
public ExceptionCode getErrorCode() {
return exceptionCode;
}
}
여기서 ExceptionCode는 다음과 같이 enum으로 미리 작성된 객체들이다.
@Getter
@RequiredArgsConstructor
public enum ExceptionCode {
// CONFLICT
CONFLICT_ID_EMAIL_NICKNAME_IN_USE(HttpStatus.CONFLICT, "사용자 아이디, 이메일 또는 닉네임이 이미 사용 중 입니다."),
CONFLICT_EMAIL_IN_USE(HttpStatus.CONFLICT, "중복된 이메일 입니다."),
CONFLICT_NICK_IN_USE(HttpStatus.CONFLICT, "중복된 닉네임 입니다."),
// FORBIDDEN
FORBIDDEN_UPDATE_ONLY_WRITER(HttpStatus.FORBIDDEN, "작성자만 수정 할 수 있습니다."),
FORBIDDEN_DELETE_ONLY_WRITER(HttpStatus.FORBIDDEN, "작성자만 삭제 할 수 있습니다."),
FORBIDDEN_YOUR_NOT_COME_IN(HttpStatus.FORBIDDEN, "권한이 없습니다."),
// NOT_FOUND
NOT_FOUND_USER(HttpStatus.NOT_FOUND, "해당 유저는 존재하지 않습니다."),
NOT_FOUND_MOVIE(HttpStatus.NOT_FOUND, "해당 영화는 존재하지 않습니다."),
NOT_FOUND_REVIEW(HttpStatus.NOT_FOUND, "해당 리뷰는 존재하지 않습니다."),
NOT_FOUND_REVIEW_COMMENT(HttpStatus.NOT_FOUND, "해당 댓글은 존재하지 않습니다."),
// BAD_REQUEST
BAD_REQUEST_ALREADY_WROTE_REVIEW(HttpStatus.BAD_REQUEST, "이미 리뷰를 작성 하셨습니다."),
BAD_REQUEST_NOT_MATCH_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다.");
private final HttpStatus httpStatus;
private final String message;
}
이렇게 구현을 했다면, ControllerAdvice에선 커스텀 예외에 대해 단 하나의 메소드만 작성해주면 된다.
@RestControllerAdvice
public class ExceptionAdviceController {
@ExceptionHandler(CustomException.class)
public ResponseEntity<ExceptionResponse> handlerException(CustomException e) {
ExceptionResponse exceptionResponse = new ExceptionResponse(e.getErrorCode());
return ResponseEntity.status(exceptionResponse.getStatus()).body(exceptionResponse);
}
}
예외가 발생하는 부분에선 다음과 같이 커스텀 예외를 발생시키며 상황에 맞는 enum을 매개변수로 넘겨준다.
public List<ReviewResponseDto> getReviewList(Long movieId) {
Movie movie = movieRepository.findById(movieId)
.orElseThrow(() -> new CustomException(ExceptionCode.NOT_FOUND_MOVIE));
return reviewRepository.findAllByMovieOrderByReviewLikeDescCreatedAtDesc(movie).stream()
.map(ReviewResponseDto::new).collect(Collectors.toList());
}