@RestControllerAdvice
@Slf4j
@RequiredArgsConstructor
public class UserExceptionHandler {
@ExceptionHandler(AlreadyExistUserException.class)
public ResponseEntity<ExceptionMessage> handle(AlreadyExistUserException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionMessage.of(e.getStatus(), e.getMessage()));
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ExceptionMessage> handle(UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionMessage.of(e.getStatus(), e.getMessage()));
}
@ExceptionHandler(UserIllegalStateException.class)
public ResponseEntity<ExceptionMessage> handle(UserIllegalStateException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionMessage.of(e.getStatus(), e.getMessage()));
}
@ExceptionHandler(UserNotAccessRightException.class)
public ResponseEntity<ExceptionMessage> handle(UserNotAccessRightException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionMessage.of(e.getStatus(), e.getMessage()));
}
}
서버를 개발하면서 에러가 발생하면 위처럼 각 커스텀 에러 클래스가 터질 때 마다
@RestControllerAdvice
클래스의 handle()
메서드로 잡는 방식으로 처리를 해주었다.
하지만 위처럼 만드니 반복되는 코드가 너무 많아져 고민이 되었고 어떻게 처리하면 좋을지 생각해보게 되었다.
@RestControllerAdvice
@Slf4j
@RequiredArgsConstructor
public class UserExceptionHandler {
@ExceptionHandler(AlreadyExistUserException.class)
public ResponseEntity<ExceptionMessage> handle(AlreadyExistUserException e) {
return getMessageResponseEntity(e.getStatus(), e.getMessage());
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ExceptionMessage> handle(UserNotFoundException e) {
return getMessageResponseEntity(e.getStatus(), e.getMessage());
}
@ExceptionHandler(UserIllegalStateException.class)
public ResponseEntity<ExceptionMessage> handle(UserIllegalStateException e) {
return getMessageResponseEntity(e.getStatus(), e.getMessage());
}
@ExceptionHandler(UserNotAccessRightException.class)
public ResponseEntity<ExceptionMessage> handle(UserNotAccessRightException e) {
return getMessageResponseEntity(e.getStatus(), e.getMessage());
}
private ResponseEntity<ExceptionMessage> getMessageResponseEntity(StatusEnum e, String e1) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionMessage.of(e, e1));
}
}
이런식으로 handle()
메서드 내부의 로직을 메서드 추출을 통해 호출하는 방식으로 바꾸어보았다.
하지만 아직도 여전히 여러개의 handle()
메서드가 존재하고 가독성이 좋지 못하다.
우선 현재의 코드 구조는
@Getter
public class AlreadyExistUserException extends RuntimeException {
private final StatusEnum status;
private static final String message = "삭제된 유저는 조회 할 수 없습니다.";
public AlreadyExistUserException(StatusEnum status) {
super(message);
this.status = status;
}
}
이렇게 커스텀 에러를 정의하는 구조를 가지고 있고
@Getter
public enum StatusEnum {
BAD_REQUEST(400, "BAD_REQUEST"),
VOTE_TYPE_NOT_MATCH(400,"VOTE_TYPE_NOT_MATCH"),
VOTE_DRINKS_DUPLICATED(400,"VOTE_DRINK_DUPLICATED"),
VOTE_NOT_FOUND(200, "VOTE_NOT_FOUND"),
TOKEN_EXPIRED(401, "TOKEN_EXPIRED"),
USER_NOT_FOUND(404, "USER_NOT_FOUND"),
COMMENT_NOT_FOUND(404, "COMMENT_NOT_FOUND"),
ALREADY_VOTE_RESULT_EXIST(403, "ALREADY_VOTE_RESULT_EXIST"),
TOKEN_NOT_EXIST(404, "TOKEN_NOT_EXIST"),
ACCESS_RIGHT_FAILED(412, "ACCESS_RIGHT_FAILED");
private final int statusCode;
private final String code;
private StatusEnum(int statusCode, String code) {
this.statusCode = statusCode;
this.code = code;
}
}
각 커스텀 에러에 맞는 상태코드와 메시지를 정의해서 사용하고 있다.
public abstract class CustomException extends RuntimeException{
public abstract StatusEnum getStatus();
public abstract String getMessage();
public CustomException(String message) {
super(message);
}
}
각 커스텀 에러 클래스를 추상화 하는 클래스를 정의했다.
public class AlreadyExistUserException extends CustomException {
private final StatusEnum status;
private static final String message = "삭제된 유저는 조회 할 수 없습니다.";
public AlreadyExistUserException(StatusEnum status) {
super(message);
this.status = status;
}
@Override
public StatusEnum getStatus() {
return status;
}
@Override
public String getMessage() {
return message;
}
}
그 이후 각 커스텀 에러 클래스들에서 위의 부모 클래스를 상속받아서 메서드를 오버라이드 해주고
@RestControllerAdvice
@Slf4j
@RequiredArgsConstructor
public class UserExceptionHandler {
@ExceptionHandler(CustomException.class)
public ResponseEntity<ExceptionMessage> handle(CustomException e) {
int statusCode = e.getStatus().getStatusCode();
return ResponseEntity.status(HttpStatus.valueOf(statusCode))
.body(ExceptionMessage.of(e.getStatus(), e.getMessage()));
}
}
기존 ExceptionHandler
에서는 그냥 CustomException
을 잡아주기만 하면 자식 예외들이 모두 잡힌다!!
더 나아가 다른 도메인에서 사용하는 예외들도 CustomException
을 상속받고 있기만 한다면
한개의 handle()
메서드로 모두 잡을 수 있게 되었다.
혹은 RunTimeException
말고 다른 기본 자바 내장 예외를 상속해야한다면 그 예외를 상속받은 공통인터페이스를 정의하고 그것을 잡는 handle()
메서드 한개만 정의해서 사용하면 된다.
한마디로 코드의 중복을 엄청나게 줄였다~!! 야호 😄
추가로)
HttpStatus.valueOf()
메서드를 통해 각 에러에 맞는HttpStatus
상태 객체도 가져와서 반환해주었다.
부모 클래스를 handler
에서 잡기만 하면 자식들 모두가 다형성으로 처리하는것을 개념적으로만 알다가
직접 적용해보니 역시 아는것과 체화는 다르다는것을 느꼈고
추상화를 통해 중복되는 코드의 양을 대폭 줄이는 것에 성공해서 기분이 좋다.
역시 이맛에 개발하지 🥳
잘 봤습니다. 좋은 글 감사합니다.