스프링 부트 3.2.1 버전을 기준으로 작성됨
종류
Exception
(예외)response.sendError(Http 상태 코드, 오류 메시지)
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
Exception 의 경우 서버 내부에서 처리할 수 없는 오류가 발생한 것으로 생각해서 HTTP 상태 코드 500을 반환
WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (response.sendError())
// 서블릿 컨테이너 기본 제공 오류화면
http://localhost:8080/error-ex
http://localhost:8080/error-404
http://localhost:8080/error-500
서블릿 기본 제공 예외 처리 화면은 고객친화적이지 않음
스프링 부트가 제공하는 기능을 통해 서블릿 오류 페이지 등록
@Component
public class WebServerCustomizer implements
WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/errorpage/404");
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
//RuntimeException 또는 그 자식 타입의 예외
ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");
factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
}
}
해당 오류를 처리할 컨트롤러가 필요
@Slf4j
@Controller
public class ErrorPageController {
@RequestMapping("/error-page/404")
public String errorPage404(HttpServletRequest request, HttpServletResponse
response) {
log.info("errorPage 404");
return "error-page/404";
}
@RequestMapping("/error-page/500")
public String errorPage500(HttpServletRequest request, HttpServletResponse
response) {
log.info("errorPage 500");
return "error-page/500";
}
}
view는 컨트롤러에서 지정한 경로에 원하는대로 만들어주면 된다.
1. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
2. WAS `/error-page/500` 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/
500) -> View
중요한점
웹 브라우저(클라이언트)는 서버 내부의 이런과정을 모른다.
오직 서버 내부에서 오류 페이지를 찾기 위해 내부적으로 재호출한다.
request.attribute에 서버가 담아준 정보
// RequestDispatcher 상수로 정의됨
ERROR_EXCEPTION = "jakarta.servlet.error.exception"; 예외
ERROR_EXCEPTION_TYPE = "jakarta.servlet.error.exception_type"; 예외 타입
ERROR_MESSAGE = "jakarta.servlet.error.message"; 오류 메시지
ERROR_REQUEST_URI = "jakarta.servlet.error.request_uri"; 클라이언트 요청 URI
ERROR_SERVLET_NAME = "jakarta.servlet.error.servlet_name"; 오류가 발생한 서블릿 이름
ERROR_STATUS_CODE = "jakarta.servlet.error.status_code"; HTTP 상태 코드
// 사용
request.getAttribute(ERROR_STATUS_CODE)
request.getDispatcherType() // REQUEST,ERROR 등 어떤 요청인지 정보가 출력됨
예외 발생 및 오류 페이지 요청 흐름에서
필터나 인터셉트가 한 번 더 호출되어 검증하는 것은 비효율적이다.
DispatcherType
으로 REQUEST
고객 요청이면 진행하고
Error
내부 호출이면 필터와 인터셉트를 건너뛰게 한다.
DispatcherType
REQUEST : 클라이언트 요청
ERROR : 오류 요청
FORWARD : MVC에서 배웠던 서블릿에서 다른 서블릿이나 JSP를 호출할 때
RequestDispatcher.forward(request, response);
INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때
RequestDispatcher.include(request, response);
ASYNC : 서블릿 비동기 호출
@Slf4j
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("log filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// ...
try {
log.info("REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
chain.doFilter(request, response);
} catch (Exception e) {
log.info("EXCEPTION {}", e.getMessage());
throw e;
} finally {
log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
}
}
@Override
public void destroy() {
log.info("log filter destroy");
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new
FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
return filterRegistrationBean;
}
}
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
기본 값 : REQUEST
사실 기본 값이 request만 필터 적용되기에 상관없다
단지 다른 type에도 적용하고싶다면 넣어주면된다.
인터셉터 중복 호출 제거
인터셉터는 스프링 제공 기능이기에 DispatcherType
과 상관없음
오류 페이지 경로를 excludePathPatterns
를 사용해서 제외하면 됨
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "*/ico", "/error", "/error-page/**"); // 오류페이지 경로
}
// 필터
}
전체 흐름
1. WAS(/error-ex, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
2. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
3. WAS 오류 페이지 확인 (WebServerCustomizer) // 오류 화면 제공 챕터 참조
4. WAS(/error-page/500, dispatchType=ERROR) -> 필터(x) -> 서블릿 -> 인터셉터(x) -> 컨트
롤러(/error-page/500) -> View
=============================================================================
/error-ex 오류 요청
필터는 DispatchType 으로 중복 호출 제거 ( dispatchType=REQUEST )
인터셉터는 경로 정보로 중복 호출 제거( excludePathPatterns("/error-page/**") )
스프링 부트는
/error
경로로 기본 오류 페이지를 설정new ErrorPage("/error")
, 상태코드와 예외를 설정하지 않으면 기본 오류 페이지로 사용됨response.sendError()
가 호출되면 모든 오류는 /error
를 호출BasicErrorController
라는 스프링 컨트롤러를 자동으로 등록ErrorPage
에서 등록한 /error
를 매핑해서 처리주의
WebServerCustomizer에 있는 @Component 를 주석처리할 것
BasicErrorController
는 기본 로직이 모두 개발된 상태
view 파일만 넣어두면 된다.
// 뷰 선택 우선순위
// 5xx, 4xx 라고 하면 500대, 400대 오류를 처리
1. 뷰 템플릿
resources/templates/error/500.html
resources/templates/error/5xx.html
2. 정적 리소스( static , public )
resources/static/error/400.html
resources/static/error/404.html
resources/static/error/4xx.html
3. 적용 대상이 없을 때 뷰 이름( error )
resources/templates/error.html
BasicErrorController
는 다음 정보를 model에 담아서 뷰에 전달
* timestamp: Fri Feb 05 00:00:00 KST 2021
* status: 400
* error: Bad Request
* exception: org.springframework.validation.BindException
* trace: 예외 trace
* message: Validation failed for object='data'. Error count: 1
* errors: Errors(BindingResult)
* path: 클라이언트 요청 경로 (`/hello`)
application.properties
server.error.include-exception=false : exception 포함 여부( true , false )
server.error.include-message=never : message 포함 여부
server.error.include-stacktrace=never : trace 포함 여부
server.error.include-binding-errors=never : errors 포함 여부
// 옵션
never : 사용하지 않음
always :항상 사용
on_param : 파라미터가 있을 때 사용
// (message=&errors=&trace=) 이런식으로 파라미터를 전달하면 model에 담김
❗ 주의
실무에서 오류 정보 사용자에게 노출 금지
server.error.whitelabel.enabled=true : 오류 처리 화면을 못 찾을 시, 스프링 whitelabel 오류 페이지 적용
server.error.path=/error : 오류 페이지 경로, 스프링이 자동 등록하는 서블릿 글로벌 오류 페이지 경로
확장 포인트
에러 공통 처리 컨트롤러의 기능 변경을 원한다면
ErrorController
or BasicErrorController
상속받아서 기능 추가
하지만 대부분 기본 기능으로 손쉽게 처리 가능
🔖 학습내용 출처