예외 처리와 오류 페이지서블릿 예외처리서블릿 예외처리 - 필터(dispatcherTypes)서블릿 예외처리 - 인터셉터스프링 부트 제공 오류 페이지API 예외 처리HandlerExceptionResolver@ExceptionHandler@ControllerAdvice, @RestControllerAdvice
예외 처리와 오류 페이지
서블릿 예외처리
스프링을 사용하지 않은, 서블릿은 다음 2가지 예외 처리를 지원한다.
- Exception
- 자바 직접 실행
- main 쓰레드가 생성되고, main()을 넘어 예외가 던져지면, 예외 정보를 남기고 해당 쓰레드는 종료된다.
- 웹 애플리케이션
- try-catch로 예외를 잡지 못하고, WAS까지 예외가 전달될 수 있다.
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
- 이런경우 톰캣이 기본적으로 제공하는 오류 화면이 노출된다
HTTP Status 404 – Not Found
HTTP Status 500 – Internal Server Error
- response.sendError(statusCode [, Error Message])
- 서블릿 컨테이너에게 오류가 발생했다는 점을 전달한다.
response.sendError(404, "404 오류!");
response.sendError(500);
WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러
- 오류 페이지는 다음과 같은 흐름으로 보여진다.
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
WAS /error-page/500 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/
- 이때 오류 정보를 request의 attribute에 추가하여 넘겨준다.
500) -> View
서블릿 예외처리 - 필터(dispatcherTypes)
- 오류 페이지를 보여줄때, 이미 완료한 필터 호출이 또 일어나면 비효율적이다. 이를 막기 위해, 서블릿은 dispatcherTypes라는 옵션을 제공한다.
public enum DispatcherType {
FORWARD, // 서블릿에서 다른 서블릿이나 JSP를 호출할 때
INCLUDE, // 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때
REQUEST, // 클라이언트 요청
ASYNC, // 서블릿 비동기 호출
ERROR // 오류 요청
}
filterRegistrationBean.setDispatcherTypes(DispatcherType.ERROR)
서블릿 예외처리 - 인터셉터
- 필터의 경우 DispatcherType를 사용할 수 있지만, 인터셉터의 경우 서블릿 제공이 아닌 스프링 제공이기 때문에 DispatcherType와 무관하게 항상 호출된다.
- 따라서 인터셉터는 오류 페이지 경로를 excludePathPatterns에 추가해서 처리해주자.
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico"
, "/error", "/error-page/**" //오류 페이지 경로
); }
스프링 부트 제공 오류 페이지
- 스프링 부트는 기본적으로
/error
경로로 자동으로 ErrorPage를 등록한다.
- BasicErrorController
- 스프링 부트에서 기본적으로 제공하는 예외 처리 컨트롤러
API 예외 처리
HandlerExceptionResolver
- HandlerExceptionResolver는 예외가 던져진 경우 해당 예외를 해결하고, 동작을 새로 정의할 수 있는 방법을 제공한다.

- HandlerExceptionResolver 인터페이스
public interface HandlerExceptionResolver {
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex);
}
- ModelAndView가 비워있으면
- View 랜더링 업이 정상 흐름으로 서블릿이 리턴됨
- ModelAndView 지정 시
- View를 랜더링하여 리턴한다
- null
- null 반환 시 다음 ExceptionResolver을 찾아 실행한다.
- HandlerExceptionResolver를 다음과 같이 활용할 수 있다.
- 예외 상태 코드 반환
- View 템플릿 처리
- API 응답 처리
- 예외를 여기까지만 처리
- WAS까지 예외가 도달하는것이 아닌, ExceptionResolver에서 예외를 처리해버림
@ExceptionHandler
- 보통 실무에서 @ExceptionHandler를 사용하여 예외처리를 진행한다.
- ExceptionHandlerExceptionResolver에서 제공한다.
- 사용법
@ExceptionHandler
어노테이션을 선언하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해준다.- 지정한 예외 또는 그 예외를 상속하는 자식 클래스는 모두 잡힌다.
- 우선순위가 존재한다. 더 자세한(지엽적인) 예외가 있다면, 해당
@ExceptionHandler
가 호출된다.
// 그냥 Exception보다 더 자세한 예외
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException e) {
log.info("handleIllegalArgument", e);
ErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER;
return ResponseEntity.status(errorCode.getHttpStatus())
.body(makeErrorResponse(errorCode));
}
// 나머지 Exception 처리
@ExceptionHandler({Exception.class})
public ResponseEntity<Object> handleAllException(Exception ex) {
log.info("handleAllException", ex);
ErrorCode errorCode = CommonErrorCode.INTERNAL_SERVER_ERROR;
return ResponseEntity.status(errorCode.getHttpStatus())
.body(makeErrorResponse(errorCode));
}
@ControllerAdvice, @RestControllerAdvice
- @ControllerAdvice 또는 @RestControllerAdvice를 사용하여 정상 코드와 예외처리 코드를 분리할 수 있다.
- @ControllerAdvice, @RestControllerAdvice의 차이는 @ResponseBody로 Json 응답을 줄지 말지의 차이이다.(@Controller - @RestController의 차이와 같다.)
- ControllerAdvic는 @ExceptionHandler를 모든 컨트롤러에 전역적으로 적용해준다.
- 따라서, 전역적으로 에러를 핸들링하는 클래스를 만들 수 있다.
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// 이제 이 ExceptionHandler들은 전역적으로 처리된다.
@ExceptionHandler(RestApiException.class)
public ResponseEntity<Object> handleCustomException(RestApiException e) {
ErrorCode errorCode = e.getErrorCode();
return handleExceptionInternal(errorCode);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<Object> handleCustomException(BadRequestException e) {
log.info("handleBadRequest", e);
ErrorCode errorCode = CommonErrorCode.BAD_REQUEST;
return handleExceptionInternal(errorCode);
}
}
- 특정 클래스나 어노테이션이 붙은 컨트롤러들을 지정할 수도 있다.
@ControllerAdvice("org.example.controllers") // 해당 클래스에만 동작
Share article