다른 팀에서 구현한 예외처리를 보고 학습했다.
여러 API 로 생기는 각각의 예외를 일관성 있게 관리할 수 있는 방식을 알아본다.
Controller 레이어에서 성공 state 를 반환할 때, ResponseEntity로 바로 반환하지 않고 반환 값을 커스텀할 수 있게 만들어지는 클래스로 제네릭 변수를 받아 dto 의 값을 status code 와 함께 반환할 수 있도록 한다.
@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class ApiResponse<T> {
@JsonProperty("isSuccess")
private final Boolean isSuccess;
private final String code;
private final String message;
@JsonInclude(JsonInclude.Include.NON_NULL)
private final T result;
// 일반적인 응답 생성
public static <T> ApiResponse<T> onSuccess(T result) {
return new ApiResponse<>(true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), result);
}
public static <T> ApiResponse<T> of(BaseCode code, T result) {
return new ApiResponse<>(true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), result);
}
// 실패한 경우 응답 생성
public static <T> ApiResponse<T> onFailure(String code, String message, T data) {
return new ApiResponse<>(false, code, message, data);
}
public static ApiResponse<String> onFailure(BaseErrorCode baseErrorCode) {
return new ApiResponse<>(false, baseErrorCode.getReasonHttpStatus().getCode(), baseErrorCode.getReasonHttpStatus().getMessage(), "null");
}
JsonPropertyOrder: Json 출력 시 필드의 순서를 지정할 수 있음.
JsonProperty: Json 출력 시 원하는 이름으로 표기
Getter
@RequiredArgsConstructor
public class ApiException extends RuntimeException {
private final BaseErrorCode errorCode;
}
BaseErrorCode 객체를 주입받아 생성될 수 있도록 만든다.
public interface BaseErrorCode {
public ErrorReasonDto getReasonHttpStatus();
}
안에서 오류 상태 코드를 불러오도록하며 ErrorReasonDto 객체로 전달하는 인터페이스 생성
@Getter
@AllArgsConstructor
public enum ErrorStatus implements BaseErrorCode {
_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500", "Interval server error"),
_BAD_REQUEST(HttpStatus.BAD_REQUEST,"400","잘못된 요청입니다."),
_BAD_REQUEST_EMAIL(HttpStatus.BAD_REQUEST,"400","중복된 이메일입니다."),
_BAD_REQUEST_USER(HttpStatus.BAD_REQUEST,"400","탈퇴한 회원입니다."),
private HttpStatus httpStatus;
private String code;
private String message;
@Override
public ErrorReasonDto getReasonHttpStatus() {
return ErrorReasonDto.builder()
.code(code)
.message(message)
.isSuccess(false)
.httpStatus(httpStatus)
.build();
}
}
enum 클래스를 활용해
httpStatuscode값을 설정
@Builder
@Getter
public class ErrorReasonDto {
private String code;
private String message;
private HttpStatus httpStatus;
private Boolean isSuccess;
}
에러 코드 및 에러 메세지와 성공유무 체크
ApiException 내부에 BaseErrorCode 즉 ErrorStatus가 존재하기에
미리 정해둔 에러 상태를 가져옴.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleRuntimeException(RuntimeException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
}
@ExceptionHandler(ApiException.class)
public ResponseEntity<ApiResponse<String>> handleCustomException(ApiException e) {
BaseErrorCode errorCode = e.getErrorCode();
return handleExceptionInternal(errorCode);
}
private ResponseEntity<ApiResponse<String>> handleExceptionInternal(BaseErrorCode errorCode) {
return ResponseEntity.status(errorCode.getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(errorCode));
}
}
ApiException 으로 발생하는 에러를 처리하는데
각 예외 안에 BaseErrorCode 인터페이스로 구현된 ErrorStatus 클래스들이 존재하기에
해당 클래스 안에서
에러 코드를 ResponseEntity 의 status 로 반환하고
에러 내용을 ApiResponse dto 객체를 활용하여 ResponseEntity 의 body 값으로 반환한다.