Spring ‐ 예외 처리 - dnwls16071/Backend_Study_TIL GitHub Wiki

📚 서블릿 예외 처리

  • Exception 발생 흐름
컨트롤러(예외 발생) → 인터셉터 → 디스패처 서블릿 → 필터 → 내장 톰켓 서버(WAS)
  • sendError 흐름
컨트롤러(예외 발생) → 인터셉터 → 디스패처 서블릿 → 필터 → 내장 톰켓 서버(WAS)
  • 예외 발생과 오류 페이지 요청 흐름
컨트롤러(예외 발생) → 인터셉터 → 디스패처 서블릿 → 필터 → WAS → WAS /error 페이지 요청 → 필터 → 디스패처 서블릿 → 인터셉터 → 컨트롤러(/error 요청)
  • 컨트롤러 측에서 예외가 발생해서 WAS까지 전파된다.
  • WAS는 오류 페이지 경로를 찾아서 내부에서 오류 페이지를 호출한다.
  • 허나 이 내부에서 오류 페이지를 호출하는 과정에서 필터와 인터셉터를 한 번 더 거치게 되는 과정이 비효율적이다.
  • 필터
    • 서블릿은 위와 같은 문제를 해결하기 위해 DispatcherType을 제공한다. 클라이언트가 요청하면 REQUEST, 에러가 전달될 때는 ERROR로 전달된다.
    • 기본적으로 필터는 REQUEST가 기본 값이기 때문에 클라이언트 요청만 필터링을 한다. 따라서 추가적인 작업을 할 필요는 없다.
  • 인터셉터
    • excludePathPatterns를 사용한다.
    • 인터셉터를 거치지 않을 URL을 지정하는 것으로 에러 컨트롤러 URL을 명시해주면 인터셉터가 거치지 않게 된다.

📚 ExceptionResolver

스크린샷 2025-01-29 오후 5 49 29

  • 스프링에서 제공하는 ExceptionResolver는 다음과 같다.
    • ExceptionHandlerExceptionResolver
    • ResponseStatusExceptionResolver
    • DefaultHandlerExceptionResolver

[ @ResponseStatus 어노테이션 적용하여 HTTP 상태 코드를 변경 ]

@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류") 
public class BadRequestException extends RuntimeException {
    // ...
}

[ ExceptionHandlerExceptionResolver - @RestControllerAdvice(@ControllerAdvice) & @ExceptionHandler]

@Data
@AllArgsConstructor
public class ErrorResult {
    private String code;
    private String message;
}

@Slf4j
@RestControllerAdvice(basePackages = "hello.exception.api")
public class ExControllerAdvice {

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandler(UserException e) {
        log.error("[exceptionHandler] ex", e);
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandler(Exception e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("EX", "내부 오류");
    }
}
  • 가장 많이 사용되는 방식으로 바로 응답값을 변환해서 내린다.
  • 클래스 단에는 @RestControllerAdvice를, 메서드 단에는 @ExceptionHandler를 붙여주고 인자로 처리하고자하는 Exception을 받아준다.
  • 메서드를 반환할 때, ResponseEntity로 반환하면서 상태값을 명시해주거나 객체를 반환하면서 @ResponseStatus를 사용해서 상태값을 명시해줘도 된다.
  • @ExceptionHandler 어노테이션은 자세한 것부터 먼저 우선권을 가진다.

📚 @ControllerAdvice

// 특정 애노테이션이 붙은 컨트롤러만 적용
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// 패키지 안에 있는 컨트롤러만 적용
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// 특정 타입의 컨트롤러만 적용
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

참고 문서 - @ControllerAdvice