- 前言
- 一、ErrorPage初始化
- 二、ErrorPage处理流程
- 三、自定义ErrorPage
前言
@ControllerAdvice 全局异常处理 和 自定义HandlerExceptionResolver 解析异常,这两种方式只能处理在处理请求请求到达了 DispatcherServlet ,并且出现了异常后进入processDispatchResult( ) 方法。
这两种方式不适用的场景:
1、请求没有到达 DispatcherServlet 的核心流程,如在 filter 中抛出异常;
2、请求进入 processDispatchResult( ) 方法处理异常,但是在处理过程中有抛出了异常,如在 @ControllerAdvice 方法中抛出了异常。
这个时候请求会进入到 ErrorPage 的处理流程。
一、ErrorPage初始化
1、ErrorPageCustomizer
实现了 ErrorPageRegistrar 接口,重写了registerErrorPages( ) 方法,用于注册 ErrorPage
(1)ErrorPageCustomizer 的定义
static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { private final ServerProperties properties; private final DispatcherServletPath dispatcherServletPath; protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) { this.properties = properties; this.dispatcherServletPath = dispatcherServletPath; } @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { //注册错误页面,默认Path 是 /error ErrorPage errorPage = new ErrorPage( this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())); errorPageRegistry.addErrorPages(errorPage); } @Override public int getOrder() { return 0; } }
public class ErrorProperties { @Value("${error.path:/error}") private String path = "/error";
(3)、创建 ErrorPageCustomizer
ErrorMvcAutoConfiguration.java @Bean public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) { return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath); }
2、ErrorPageRegistrarBeanPostProcessor
ErrorPageRegistrar 的后置处理器,注册错误页面到web容器
public class ErrorPageRegistrarBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { private ListableBeanFactory beanFactory; private Listregistrars; @Override public void setBeanFactory(BeanFactory beanFactory) { Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, "ErrorPageRegistrarBeanPostProcessor can only be used with a ListableBeanFactory"); this.beanFactory = (ListableBeanFactory) beanFactory; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ErrorPageRegistry) { //bean是 ErrorPageRegistry 类型 postProcessBeforeInitialization((ErrorPageRegistry) bean); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } private void postProcessBeforeInitialization(ErrorPageRegistry registry) { //调用 ErrorPageRegistrar 的 registerErrorPages() 注册错误页面 for (ErrorPageRegistrar registrar : getRegistrars()) { registrar.registerErrorPages(registry); } } //从beanFactory中获取 ErrorPageRegistrar.class 类型的类,通过 order 排序 private Collection getRegistrars() { if (this.registrars == null) { // Look up does not include the parent context this.registrars = new ArrayList<>( this.beanFactory.getBeansOfType(ErrorPageRegistrar.class, false, false).values()); this.registrars.sort(AnnotationAwareOrderComparator.INSTANCE); this.registrars = Collections.unmodifiableList(this.registrars); } return this.registrars; } }
ErrorPage 被添加到了 web容器中
AbstractConfigurableWebServerFactory.java public void addErrorPages(ErrorPage... errorPages) { Assert.notNull(errorPages, "ErrorPages must not be null"); this.errorPages.addAll(Arrays.asList(errorPages)); }
3、将 ErrorPage 添加到 DeploymentInfo 中
我这里使用的容器是 Undertow 服务,在创建服务的过程中
refresh( ) -> onRefresh( ) -> createWebServer( ) -> getWebServer( ) -> createManager( ) -> configureErrorPages( )
private void configureErrorPages(DeploymentInfo servletBuilder) { //获取到刚才注册的 ErrorPage for (ErrorPage errorPage : getErrorPages()) { servletBuilder.addErrorPage(getUndertowErrorPage(errorPage)); } }
封装成 undertow api 的 ErrorPage
private io.undertow.servlet.api.ErrorPage getUndertowErrorPage(ErrorPage errorPage) { if (errorPage.getStatus() != null) { return new io.undertow.servlet.api.ErrorPage(errorPage.getPath(), errorPage.getStatusCode()); } if (errorPage.getException() != null) { return new io.undertow.servlet.api.ErrorPage(errorPage.getPath(), errorPage.getException()); } return new io.undertow.servlet.api.ErrorPage(errorPage.getPath()); }
添加到 DeploymentInfo 缓存起来
DeploymentInfo .java public DeploymentInfo addErrorPage(final ErrorPage errorPage) { this.errorPages.add(errorPage); return this; }
4、
configureErrorPages( )执行后, ->manager.deploy(); -> initializeErrorPages(deployment, deploymentInfo);
(1)initializeErrorPages()
private void initializeErrorPages(final DeploymentImpl deployment, final DeploymentInfo deploymentInfo) { final Mapcodes = new HashMap<>(); final Map , String> exceptions = new HashMap<>(); String defaultErrorPage = null; for (final ErrorPage page : deploymentInfo.getErrorPages()) { if (page.getExceptionType() != null) { //exceptions 保存异常类型和路径的映射 exceptions.put(page.getExceptionType(), page.getLocation()); } else if (page.getErrorCode() != null) { //codes 保存异常状态码和路径的映射 codes.put(page.getErrorCode(), page.getLocation()); } else { //默认异常路径只能有一个 if (defaultErrorPage != null) { throw UndertowServletMessages.MESSAGES.moreThanOneDefaultErrorPage(defaultErrorPage, page.getLocation()); } else { defaultErrorPage = page.getLocation(); } } } deployment.setErrorPages(new ErrorPages(codes, exceptions, defaultErrorPage)); }
5、BasicErrorController
请求出现错误时,且没有被其他的全局异常处理拦截到,会将请求流转到ErrorController 中的 /error 和 /errorHtml 接口
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List二、ErrorPage处理流程errorViewResolvers) { super(errorAttributes, errorViewResolvers); Assert.notNull(errorProperties, "ErrorProperties must not be null"); this.errorProperties = errorProperties; } @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity
当请求出现错误时,错误被抛给Undertow时,错误会被Undertow拦截到,下面从Undertow拦截到错误开始分析
1、handleFirstRequest( )
try { ... } } catch (Throwable t) { servletRequestContext.setRunningInsideHandler(false); AsyncContextImpl asyncContextInternal = servletRequestContext.getOriginalRequest().getAsyncContextInternal(); if(asyncContextInternal != null && asyncContextInternal.isCompletedBeforeInitialRequestDone()) { asyncContextInternal.handleCompletedBeforeInitialRequestDone(); } if(asyncContextInternal != null) { asyncContextInternal.initialRequestFailed(); } //by default this will just log the exception boolean handled = exceptionHandler.handleThrowable(exchange, request, response, t); if(handled) { exchange.endExchange(); } else if (request.isAsyncStarted() || request.getDispatcherType() == DispatcherType.ASYNC) { //异步请求 exchange.unDispatch(); servletRequestContext.getOriginalRequest().getAsyncContextInternal().handleError(t); } else { if (!exchange.isResponseStarted()) { //重置 response response.reset(); //reset the response exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); //清理了响应头,这里很重要,缺少了响应头,可能会引起一些其他的错误 exchange.getResponseHeaders().clear(); //获取新的请求错误路径 String location = servletContext.getDeployment().getErrorPages().getErrorLocation(t); if (location == null) { location = servletContext.getDeployment().getErrorPages().getErrorLocation(StatusCodes.INTERNAL_SERVER_ERROR); } if (location != null) { RequestDispatcherImpl dispatcher = new RequestDispatcherImpl(location, servletContext); try { //将请求分发到错误路径上,也就是访问默认异常控制器 BasicErrorController dispatcher.error(servletRequestContext, request, response, servletRequestContext.getOriginalServletPathMatch().getServletChain().getManagedServlet().getServletInfo().getName(), t); } catch (Exception e) { UndertowLogger.REQUEST_LOGGER.exceptionGeneratingErrorPage(e, location); } } else { if (servletRequestContext.displayStackTraces()) { ServletDebugPageHandler.handleRequest(exchange, servletRequestContext, t); } else { servletRequestContext.getOriginalResponse().doErrorDispatch(StatusCodes.INTERNAL_SERVER_ERROR, StatusCodes.INTERNAL_SERVER_ERROR_STRING); } } } } } finally { ... }
2、getErrorLocation( )
优先根据异常类型查找路径,不存在时再根据状态码查找路径,都不存在时使用默认路径
public String getErrorLocation(final Throwable exception) { if (exception == null) { return null; } //todo: this is kinda slow, but there is probably not a great deal that can be done about it String location = null; for (Class c = exception.getClass(); c != null && location == null; c = c.getSuperclass()) { location = exceptionMappings.get(c); } if (location == null && exception instanceof ServletException) { Throwable rootCause = ((ServletException) exception).getRootCause(); //Iterate through any nested JasperException in case it is in JSP development mode while (rootCause != null && rootCause instanceof ServletException && location == null) { for (Class c = rootCause.getClass(); c != null && location == null; c = c.getSuperclass()) { location = exceptionMappings.get(c); } rootCause = ((ServletException) rootCause).getRootCause(); } if (rootCause != null && location == null) { for (Class c = rootCause.getClass(); c != null && location == null; c = c.getSuperclass()) { location = exceptionMappings.get(c); } } } if (location == null) { location = getErrorLocation(StatusCodes.INTERNAL_SERVER_ERROR); } return location; }三、自定义ErrorPage
1、自定义GlobalErrorPageRegistrar
可以通过状态码或者具体的异常类型对应要访问的路径
@Configuration @Slf4j public class GlobalErrorPageRegistrar implements ErrorPageRegistrar { @Override public void registerErrorPages(ErrorPageRegistry registry) { //状态码和路径映射 registry.addErrorPages( new ErrorPage(HttpStatus.BAD_REQUEST, "/400"), new ErrorPage(HttpStatus.NOT_FOUND, "/404"), new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500") ); //异常类型和路径映射 registry.addErrorPages( new ErrorPage(IllegalArgumentException.class, "/400"), new ErrorPage(HttpTimeoutException.class, "/408") ); } }
2、ErrorPageController
配置错误的请求对应的方法
@RestController public class ErrorPageController { @RequestMapping(value = "/400", produces = {MediaType.APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseEntity to400() { return new ResponseEntity(400, "请求有误"); } @RequestMapping(value = "/404", produces = {MediaType.APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.NOT_FOUND) public ResponseEntity to404() { return new ResponseEntity(404, "找不到资源"); } @RequestMapping(value = "/408", produces = {MediaType.APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.REQUEST_TIMEOUT) public ResponseEntity to408() { return new ResponseEntity(408, "请求超时"); } @RequestMapping(value = "/500", produces = {MediaType.APPLICATION_JSON_VALUE}) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseEntity to500() { return new ResponseEntity(500, "服务器错误"); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)