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

- 스프링에서 제공하는 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