@PostMapping("")
@Operation(summary = "[A] 식단 생성", description = "새로운 식단 생성")
public ResponseEntity<?> createMeal(
@RequestBody MealDto mealDto
) {
Long id = mealService.createMeal(mealDto);
return ResponseEntity.ok()
.body(id+"번 식단이 생성되었습니다.");
}
스프링부트에 입문하면 위와 같은 방식으로 ResponseEntity를 사용하여 응답하게 된다. ResponseEntity를 사용할 때 단점을 여러가지가 있지만, 그 중 나에게 다가왔던 단점은 바로 예외처리였다.
예외 응답을 작성할 때 세부적인 처리가 어려웠고 특히 아래와 같이 긴 에러 문구가 나온다는 점이 불편하게 다가왔다.

그래서 내가 사용한 방법은 공통 응답 모델을 만들어, 보다 의미 있는 예외처리를 하고자 했다.
아래 사진처럼 status, data, message를 body로 받도록 설계하였다.
status에는 요청을 성공적으로 처리하였는지 여부가 "success" 또는 "error"로 응답되며, message에는 예외가 발생한 경우 전달할 메시지를 전달하게 된다.

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ApiResponse<T> {
private static final String SUCCESS_STATUS = "success";
private static final String ERROR_STATUS = "error";
private String status;
private T data;
private String message;
public static <T> ApiResponse<T> success(T data, String message) {
return new ApiResponse<>(SUCCESS_STATUS, data, message);
}
public static ApiResponse<?> error(String message) {
return new ApiResponse<>(ERROR_STATUS, null, message);
}
private ApiResponse(String status, T data, String message) {
this.status = status;
this.data = data;
this.message = message;
}
}
발생한 예외 종류에 따른 예외처리를 해주었다. GlobalExceptionHandler를 생성하여 예외처리를 해준다. @ExceptionHandler 어노테이션을 사용, 자바 및 스프링에서 자주 발생하는 예외를 핸들링하도록 하였다. 또한 예외 발생 시, 로그가 많이 쌓이는 것을 방지 하기 위해 딱 필요한 세 줄만 남기도록 하였다.
log.error("Exception type: {}", exception.getClass().toGenericString());
log.error("Exception message: {}", exception.getMessage());
log.error("Exception point: {}", exception.getStackTrace()[0]);
전체 코드는 다음과 같다.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({
ArrayIndexOutOfBoundsException.class,
ArithmeticException.class,
ClassCastException.class,
ConcurrentModificationException.class,
DateTimeParseException.class,
FileNotFoundException.class,
HttpMessageNotReadableException.class,
HttpClientErrorException.BadRequest.class,
IllegalArgumentException.class,
IllegalStateException.class,
InterruptedException.class,
IOException.class,
JsonProcessingException.class,
MethodArgumentNotValidException.class,
MissingRequestHeaderException.class,
MissingServletRequestParameterException.class,
NoSuchElementException.class,
NoHandlerFoundException.class,
NullPointerException.class,
ParseException.class,
SecurityException.class,
UnsupportedOperationException.class,
Exception.class
})
protected ResponseEntity<ApiResponse<?>> handleException(Exception exception) {
log.error("Exception type: {}", exception.getClass().toGenericString());
log.error("Exception message: {}", exception.getMessage());
log.error("Exception point: {}", exception.getStackTrace()[0]);
ErrorCode errorCode = determineResponseStatus(exception);
return ResponseEntity.status(errorCode.getHttpStatus())
.body(ApiResponse.error(exception.getMessage()));
}
private ErrorCode determineResponseStatus(Exception exception) {
if (exception instanceof ArrayIndexOutOfBoundsException ||
exception instanceof DateTimeParseException ||
exception instanceof FileNotFoundException ||
exception instanceof IllegalArgumentException ||
exception instanceof InterruptedException ||
exception instanceof JsonProcessingException ||
exception instanceof MethodArgumentNotValidException ||
exception instanceof MissingRequestHeaderException ||
exception instanceof MissingServletRequestParameterException ||
exception instanceof NoHandlerFoundException ||
exception instanceof NullPointerException ||
exception instanceof ParseException ||
exception instanceof SecurityException) {
return ErrorCode.INTERNAL_SERVER_ERROR;
} else if (exception instanceof ClassCastException ||
exception instanceof ConcurrentModificationException ||
exception instanceof HttpMessageNotReadableException ||
exception instanceof ArithmeticException ||
exception instanceof IllegalStateException ||
exception instanceof IOException ||
exception instanceof HttpClientErrorException.BadRequest) {
return ErrorCode.BAD_REQUEST_ERROR;
}else if (exception instanceof NoSuchElementException ||
exception instanceof UnsupportedOperationException) {
return ErrorCode.NOT_FOUND_ERROR;
} else {
return ErrorCode.INTERNAL_SERVER_ERROR;
}
}
}
@Getter
@AllArgsConstructor
public enum ErrorCode {
BAD_REQUEST_ERROR(HttpStatus.BAD_REQUEST),
NOT_FOUND_ERROR(HttpStatus.NOT_FOUND),
NULL_POINT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),
IO_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR);
private final HttpStatus httpStatus;
}
이제 ResponseEntity를 사용하지 않고 내가 만든 ApiResponse를 적용해보았다.
@PostMapping("")
@Operation(summary = "[A] 식단 생성", description = "새로운 식단 생성")
public ApiResponse<?> createMeal(
@RequestBody MealDto mealDto
) {
Long id = mealService.createMeal(mealDto);
return ApiResponse.success(id, "식단이 성공적으로 등록되었습니다.");
}


