Global Exception Handler

GEONNY·2024년 7월 30일

Building-API

목록 보기
11/28
post-thumbnail

현재 구현된 API 에서 Exception 이 발생하게 되면 응답으로 500 Internal Server Error 가 전달 되는 것을 이전 Create API Operation(2) - Search by condition 에서 확인했었습니다.
Operaion 에서 발생 할 수 있는 모든 예외를 처리하는 것도 쉬운 일은 아니며, 공통 적으로 발생할 수 있는 예외에 대한 처리 코드를 모든 Operation 에 추가하는 것도 비효율적입니다. (500 Internal Server Error 발생은 API 개발자 입장에서 발생 시키지 말아야할 오류 입니다.)
Springboot 에서는 원하는 모든 API Operation 에서 발생 하는 예외에 대하여 공통으로 처리할 수 있는 방법을 ControllerAdvice 를 통해서 제공하고 있습니다.
Link : https://docs.spring.io/spring-boot/reference/web/servlet.html#web.servlet.spring-mvc.error-handling

📌ControllerAdvice

ControllerAdvice 는 이름에서도 유추 할 수 있듯이 Controller 에서 발생하는 예외를 AOP 와 유사한 방식으로 catch 하는 Annotation 입니다. Spring AOP 에 대해서 알고있는 분이라면 Controller에 AfterThrowing 설정을 추가 한건가? 정도 예상하실 수 있을 거에요. AfterThrowing 과 유사하게 동작하긴 하지만, 이를 사용하는 것은 아닙니다.
Controller 에서 Exception 이 발생하면, ExceptionHandlerExceptionResolver 가 Exception 을 가로채고, 발생한 Exception 에 대한 예외처리 메서드가 있는지 @ExceptionHandler 를 검색하여 해당 메서드를 실행합니다. AOP 와 유사한 방식으로 동작하지만 실제 구현은 Spring MVC 의 예외처리 매커니즘을 기반으로 합니다.

📌create common error response

공통적으로 Exception 이 발생했을 때 응답할 record 를 생성합니다. 기존 공통 응답 구조체들과 같이 status, message 를 추가하고, 여기에 상세 메시지를 전달할 detailMessage 도 추가합니다.
common.response.ErrorResponse

@Builder
public record ErrorResponse(
        String status,
        String message,
        String detailMessage
) {
}

이제 위에서 설명한 ControllerAdvice 와 ExceptionHandler 를 사용하여 Controller 에서 발생하는 Exception 을 처리할 GlobalExceptionHandler 를 생성합니다.
이에 앞서, message.properties 에 실패 관련 status와 message 를 추가합니다.

📌Add Error Message

message.properties, message_en_US.properties, message_zh_CN.properties

📌GlobalExceptionHandler

common.exception.GlobalExceptionHandler

@RestControllerAdvice
@RequiredArgsConstructor
@Slf4j
public class GlobalExceptionHandler {

    private final MessageConfig messageConfig;
    
    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleEntityNotFound(EntityNotFoundException e) {
    	log.error(e.getMessage(), e);
        return ResponseEntity.ok()
                .body(ErrorResponse.builder()
                        .status(messageConfig.getMessage("FAIL.CODE"))
                        .message(messageConfig.getMessage("FAIL.NO.DATA.MESSAGE"))
                        .detailMessage(e.getMessage())
                        .build());
    }
}

http://localhost:13713/my-api/members/member2 를 요청하여 EntityNotFoundException 을 발생시켜봅니다.

{
    status: "API_FAIL",
    message: "데이터가 존재하지 않습니다.",
    detailMessage: "회원 ID 가 존재하지 않습니다. -> member2"
}

이전에 500 Internal Server Error 가 발생한 것과 다르게 HTTP Status 가 200 OK 로 ErrorResponse 가 전달된 것을 확인하실 수 있습니다.

GlobalExceptionHandler 개선 - 응답 생성 메서드 분리

📚참고

📕AOP (Aspect Oriented Programming)

AOP 는 OOP(Oriented Object Programming : 객체 지향 프로그래밍) 의 단점을 보완하여 특정 시점에 공통된 기능을 동작하게 해, 코드의 효율성을 높일 수 있는 Spring 의 기능입니다. Controller 를 감싼 Proxy Pattern 으로 동작하며, dispatcher-servlet 이후에 동작하기 때문에 Request, Response, Parameter, Spring bean 에 모두 접근 가능합니다.
@Before, @After, @AfterReturning, @AfterThrowing, @Around 를 통해서 Controller 의 특정 시점에 method 를 실행할 수 있습니다.

구분시점
@BeforeController 실행 전
@AfterController 실행 후
@AfterReturningController 정상 응답 후
@AfterThrowingController 에서 Exception 이 발생한 후
@Around모든 시점

📙@ControllerAdvice vs @RestControllerAdvice

같은 동작을 하는 두개의 Annotation 이 있습니다. 차이는 적용대상과 응답타입에 있습니다. @ControllerAdvice 는 모든 Controller에 적용됩니다. (@Controller, @RestController) 하지만 @RestControllerAdvice 는 @RestController 에만 적용이 됩니다. 적용되는 Controller 차이에서도 알수 있듯이 @ControllerAdvice 는 view와 Json 모두 전달이 가능하고 @RestControllerAdvice 는 view 를 전달 할 수 없습니다.

구분적용 대상응답 타입
@ControllerAdvice@Controller, @RestControllerView, JSON, XML
@RestControllerAdvice@RestControllerJSON, XML

📘Springboot 의 ExceptionResolver

ExceptionResolver 는 Springboot에서 예외를 처리하고 적절한 응답을 반환하는 중요한 역할을 합니다. Spring 에서는 HandlerExceptionResolver interface 를 구현한 3개의 ExceptionResolver 를 제공하고 위에서 사용한 @ExceptionHandler 를 사용하는 ExceptionHandlerExceptionResolver 가 그 중 하나입니다.

  • ExceptionHandlerExceptionResolver : @ExceptionHandler 를 사용하여 예외 처리 메서드를 호출합니다.
  • ResponseStatusExceptionResolver : @ResponseStatus 이 있으면 해당 Annocation에 정의된 HTTP status code를 지정합니다.
  • DefaultHandlerExceptionResolver : NoSuchRequestHandlingMethodException, HttpRequestMethodNotSupportedException, HttpMediaTypeNotSupportedException 등과 같은 표준 Spring exception을 처리합니다.

ExceptionHandlerExceptionResolver -> ResponseStatusExceptionResolver -> DefaultHandlerExceptionResolver 순서로 동작합니다.

profile
Back-end developer

0개의 댓글