예외발생 흐름
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
sendError 흐름
=>response.sendError(HTTP 상태코드, 오류메시지)
WAS(SendError 호출기록확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러
오류페이지 요청흐름
WAS(오류페이지 다시 요청) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(에러페이지 컨트롤러) -> view(오류페이지)
정리하자면 예외가 발생시 WAS까지 전파후 오류페이지를 찾아서 다시 오류 페이지를 호출하는 방식
컨트롤러에서 예외가 발생하기전에 필터나 인터셉터를 호출 하였는데
이후에 예외가 발생하고 오류페이지를 요청시 다시한번 호출해야하는데
해당 필터나 인터셉터가 한번 더 호출되는 것은 비효율적이다.
필터
DispatcherType
필터는 위에 말한 비효율적인 방법을 개선하기 위해 해당 옵션을 제공함.1. REQUEST : 클라이언트 요청 2. ERROR : 오류 요청 3. FORWARD : 서블릿에서 다른 서블릿이나 JSP 호출시 4. INCLUDE : 서블릿에서 다른 서블릿이나 JSP 결과를포함할때 5. ASYN : 서블릿 비동기 호출 filterRegistration.setDispatcherType(DispatcherType.REQUEST, DispatcherType.ERROR); 필터 등록시 setDispatcherType에서 허용 타입을 설정이 가능하다. 기본값은 REQUEST만 허용됨.
인터셉터
인터셉터 같은 경우 서블릿이 제공하는 기능이아니라 스프링이 제공하는 기능이다. 그래서 DispatcherType과는 무관하게 항상 호출한다.
=> 인터셉터 등록시 사용한 excludePathPatterns를 사용해서 제외가 가능하다.
registry.excludePathPatterns("/error") //오류 페이지 경로
스프링부트 오류페이지
스프링 부트사용시 오류가 발생했을 때 "/error"를 기본으로 요청한다.
스프링부트가 자동 등록한 'BasicErrorController'가 이경로를 기본으로 받는다.
뷰선택 우선순위
1. 뷰템플릿
- resource/templates/error/500.html
- resource/templates/error/5xx.html
2. 정적 리소스 (static,publc)
- resource/static/error/400.html
3. 적용대상이 없을때
- resource/templacte/erro.html
해당 경로에 HTTP 상태코드 이름의 뷰파일을 넣어두면 된다.
뷰템플릿은 정적리소스보다 우선순위가 높고, 404나500처럼 구체적인 것이 5xx처럼 덜 구체적인것 보다 우선순위가 높다.
HTML은 스프링부트에서 /error 에 오류페이지만 만들면 대부분의 문제를 해결 할 수 있다.
API같은 경우에는 각 오류상황에 맞는 오류 응답 스펙을 정하고, JSON으로 데이터를 내려줘야한다.
스프링이 제공하는 ExceptionResolver
HandlerExceptionResolverComposite에 등록
1. ExceptionHandlerExceptionResolver
2. ResponseStatusExceptionResolver
3.DefaultHandlerExceptionResolver ->우선순위가 가장 낮다.
- ExceptionHandlerExceptionResolver
@ExceptionHandler를 처리한다. API예외 대부분이 이기능으로 처리- ResponseStatusExceptionResolver
HTTP상태코드를 지정해준다.
@ResponseStatus(value = HttpStatus.NOT_FOUND)- DefaultHandlerExceptionResolver
스프링 내부 기본 예외를 처리한다.
ResponseStatusExceptionResolver
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청")
public class BadRequestException extends RuntimeException{}
public String responseStatus(){
throw new ResponseStatusException(HttpStatus.NOT_FOUNT,"error.bad", new IllegalArgumentException());
}
DefaultHandlerExceptionResolver
스프링 내부에서 발생하는 스프링 예외를 해결한다.
대표적으로 파라미터 바인딩시 타입이 맞지 않으면 내부에서 TypeMismatchException이 발생하는데 예외가 발생했기 때문에 그냥 두면
서블릿까지 올라가서 500에러가 발생하지만, 이 파라미터 바인딩 같은 경우에는
대부분 클라이언트의 실수로 발생하는 문제여서 400오류로 변경한다.
ExceptionHandlerExceptionResolver
@ExceptionHandler
해당 컨트롤러에 처리하고 싶은 예외를 지정해주면 된다.
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExhandle(IllegalArgumentException e){
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler //예외 생략시 메서드 파라미터가 예외가 지정됨.
public ErrorResult illegalExhandle(IllegalArgumentException e){
return new ErrorResult("BAD",e.getMessage); //예외발생시 API응답객체
}
//실행흐름
1. 컨트롤러가 호출한 결과 IllegalArgumentException 예외가 컨트롤러 밖으로 던져진다.
2. 예외가 발생했으므로 ExceptionResolver가 작동,
우선순위가 높은 ExceptionHandlerExceptionResolver가 실행
3. ExceptionHandlerExceptionResolver 는 해당 컨트롤러에
IllegalArgumentException을 처리할 수 있는 @ExceptionHandler가 있는지 확인
4. illegalExhandle() 실행, @ResponseStatus(HttpStatus.BAD_REQUEST) 지정했으므로 400으로 응답
@ControllerAdivce 컨트롤 지정방법
//애노테이션
@ControllerAdivce(annotations = RestController.class)
public class ExampleAdvice1{}
//특정 패키지
@ControllerAdivce("org.example.controllers")
public class ExampleAdvice2{}
//특정 클래스
@ControllerAdivce(assignableType =
{ControllerInterface.class, AbstractController.calss})
public class ExampleAdvice3{}