apache 设置404 页面_SpringBoot自定义错误页面

SpringBoot请求错误如404可能看到如下页面:

67aa91d47af80c05681f1bfeeb5c664a.png


有时可能需要自定义错误页面针对不同的http.status,如404/400。

【1】解决方法

① 注册错误页面

如下所示:

@Componentpublic class ErrorPageConfig implements ErrorPageRegistrar {    @Override    public void registerErrorPages(ErrorPageRegistry registry) {        ErrorPage error400Page = new ErrorPage(HttpStatus.BAD_REQUEST, "/error/404");        ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");        ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");        registry.addErrorPages(error400Page,error404Page,error500Page);    }}

② controller进行拦截

然后你只需要写个controller拦截不同请求然后跳到不同的自定义错误页面即可,如下所示:

@RequestMapping("/error/{status}")public String errorPage(@PathVariable Integer status){    switch (status){        case 401:        case 400:return "/error/404";        case 500:return "/error/500";        default:return "/error/default";    }}

那么原理呢?

【2】原理讲解

① 启动SpringBoot,注册错误页面

如下图所示,启动项目时候再onRefresh方法中会创建一个WebServer,继而获取ServletWebServerFactory。

8bfd720c1a870d96c4c0960e9066aeeb.png

1.1AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization

在创建bean-tomcatServletWebServerFactory时会调用AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization,如下所示:

@Overridepublic Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) {  return result; } result = current;}return result;}

该方法会获取bean后置处理器,然后循环遍历调用每个bean后置处理器的postProcessBeforeInitialization方法。

1.2 ErrorPageRegistrarBeanPostProcessor.postProcessBeforeInitialization

当遍历到ErrorPageRegistrarBeanPostProcessor时会调用其postProcessBeforeInitialization方法,方法源码如下所示:

@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ErrorPageRegistry) {  postProcessBeforeInitialization((ErrorPageRegistry) bean); } return bean;}

方法会判断当前bean是否ErrorPageRegistry类型,如果是,则调用postProcessBeforeInitialization方法,源码如下所示:

private void postProcessBeforeInitialization(ErrorPageRegistry registry) { for (ErrorPageRegistrar registrar : getRegistrars()) {  registrar.registerErrorPages(registry); }}

该方法会获取Registrars,然后循环遍历调用每一个注册器的registerErrorPages方法。获取注册其源码如下所示:

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;}

故而,当我们的ErrorPageConfig 实现了ErrorPageRegistrar时,会被检测到并执行registerErrorPages方法。

eabd7b41086bac41873991303f8d3175.png
4aa23f0edf26fb672441dcf9c1b4e429.png

② 把错误页面放到StandardContext.errorPageSupport中

StandardContext是什么?我们可以看下如下类继承示意图。

b74cf564b64d43bbc1a3d3e013918378.png

在①中我们提到会注册错误页面registrar.registerErrorPages(registry);,如下图所示此时的registry为TomcatServletWebServerFactory:

4510b0e2a19d423ad803d74fb603d000.png

我们再来看下TomcatServletWebServerFactory继承示意图(可以看到其父类AbstractConfigurableWebServerFactory实现了ErrorPageRegistry接口):

d9ac125baf2a99754b0a5f03137b40b6.png

2.1 AbstractConfigurableWebServerFactory.addErrorPages

方法源码如下:

@Overridepublic void addErrorPages(ErrorPage... errorPages) { Assert.notNull(errorPages, "ErrorPages must not be null"); this.errorPages.addAll(Arrays.asList(errorPages));}

也就说错误页面现在被放到了属性private Set errorPages = new LinkedHashSet<>();中。

2.2 TomcatServletWebServerFactory.configureContext

创建完TomcatServletWebServerFactory后会调用configureContext方法,如下图所示:

1ac15c84be8ec549b0693cca4e999686.png


在configureContext方法中会获取错误页面然后逐个调用StandardContext.addErrorPage方法添加到其ErrorPageSupport errorPageSupport中。

configureContext方法中遍历错误页面如下所示:

for (ErrorPage errorPage : getErrorPages()) { org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage(); tomcatErrorPage.setLocation(errorPage.getPath()); tomcatErrorPage.setErrorCode(errorPage.getStatusCode()); tomcatErrorPage.setExceptionType(errorPage.getExceptionName()); context.addErrorPage(tomcatErrorPage);}

StandardContext.addErrorPage方法源码如下所示:

@Override public void addErrorPage(ErrorPage errorPage) {     // Validate the input parameters     if (errorPage == null)         throw new IllegalArgumentException             (sm.getString("standardContext.errorPage.required"));     String location = errorPage.getLocation();     if ((location != null) && !location.startsWith("/")) {         if (isServlet22()) {             if(log.isDebugEnabled())                 log.debug(sm.getString("standardContext.errorPage.warning",                              location));             errorPage.setLocation("/" + location);         } else {             throw new IllegalArgumentException                 (sm.getString("standardContext.errorPage.error",                               location));         }     }//调用errorPageSupport.add     errorPageSupport.add(errorPage);     fireContainerEvent("addErrorPage", errorPage); }

ErrorPageSupport.add方法如下所示:

public void add(ErrorPage errorPage) {     String exceptionType = errorPage.getExceptionType();     if (exceptionType == null) {         statusPages.put(Integer.valueOf(errorPage.getErrorCode()), errorPage);     } else {         exceptionPages.put(exceptionType, errorPage);     } }

通过该方法可以看到,不止可以通过HTTP状态码定义错误页面,还可以通过异常类型进行定义。

那么ErrorPageSupport、statusPages、exceptionPages分别是什么呢?我们看下图示意:

33f17b9d3695a2b22566235831676bee.png

③ 错误页面如何被用到

在ResourceHttpRequestHandler.handleRequest方法处理请求时,找不到资源会调用response.sendError方法:

eb153545254faf813ea617d1f6c01121.png


这里只需要关注这一点,无需关注细节,我们继续往下走。。。。一直走到StandardHostValve.status方法。

StandardHostValve.status中会对响应状态码进行处理。

private void status(Request request, Response response) {     int statusCode = response.getStatus();     // Handle a custom error page for this status code     Context context = request.getContext();     if (context == null) {         return;     }     /* Only look for error pages when isError() is set.      * isError() is set when response.sendError() is invoked. This      * allows custom error pages without relying on default from      * web.xml.      */     if (!response.isError()) {         return;     }//这里会从errorPageSupport.find(errorCode)获取到错误页//根据错误码,比如404从statusPages获取对应的ErrorPage对象     ErrorPage errorPage = context.findErrorPage(statusCode);     if (errorPage == null) {         // Look for a default error page         errorPage = context.findErrorPage(0);     }     if (errorPage != null && response.isErrorReportRequired()) {         response.setAppCommitted(false);         request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,                           Integer.valueOf(statusCode));         String message = response.getMessage();         if (message == null) {             message = "";         }         request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);         request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,                 errorPage.getLocation());         request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,                 DispatcherType.ERROR);         Wrapper wrapper = request.getWrapper();         if (wrapper != null) {             request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,                               wrapper.getName());         }         request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,                              request.getRequestURI());                              //这里很重要,将会尝试跳转到我们自定义错误请求页面         if (custom(request, response, errorPage)) {             response.setErrorReported();             try {                 response.finishResponse();             } catch (ClientAbortException e) {                 // Ignore             } catch (IOException e) {                 container.getLogger().warn("Exception Processing " + errorPage, e);             }         }     } }

如下图所示,在StandardHostValve.custom方法中将会调用ApplicationDispatcher.forwar进行请求转发。

b12bc9d98e8e7d83d7881fe8bdc3223a.png

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:

https://blog.csdn.net/j080624/article/details/109197726

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/261892.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 编程:如何提高性能?(简单总结篇)

2019独角兽企业重金招聘Python工程师标准>>> 开发者在编程中除了要有编程规范&#xff0c;还要注意性能&#xff0c;在 Java 编程中有什么提高性能的好办法呢&#xff1f; 本文转自国内 ITOM 行业领军企业 OneAPM Cloud Insight&#xff08;一款能够优雅监控多种操作…

mysql没有开启binlog能恢复数据吗_神了!一个妹子 rm -rf 把公司整个数据库删没了......

经历了两天不懈努力&#xff0c;终于恢复了一次误操作删除的生产服务器数据。对本次事故过程和解决办法记录在此&#xff0c;警醒自己&#xff0c;也提示别人莫犯此错。也希望遇到问题的朋友能找到一丝灵感解决问题。01事故背景安排一个妹子在一台生产服务器上安装 Oracle&…

android手机活跃度,微信Android机型活跃度曝光,这个结果你满意吗?

原标题&#xff1a;微信Android机型活跃度曝光&#xff0c;这个结果你满意吗&#xff1f;在本周的第二届前端开发者大会上&#xff0c;由腾讯微信工程师公布了微信Android客户端机型前十的发布图&#xff0c;说的也是某款手机的存量跟用户活跃度的统计&#xff0c;在这张微信分…

postgresql9.4.4中文手册笔记-9.10 支持枚举函数

2019独角兽企业重金招聘Python工程师标准>>> 创建枚举类型 create type name as ENUM(zhao,qian,sun,li,zhou,wu, zheng,wang,cheng); 支持枚举函数 select enum_first(null::testenum); --查询枚举类型第一个元素 enum_first ------------zhao (1 row) select e…

.NET Framework源码研究系列之---Delegate

前言 曾几何时能看到微软产品的源码简直是天方夜谭,不过现在这却成了现实,微软终于对外开放了它的产品的源代码.抛去开源运动与微软之间的世代情仇,抛去微软这一做法的初衷,这总归是件好事,能够让我们拨开云雾,一窥优秀产品的秘密. 前两天看到有位仁兄在随笔中的留言,说他以为&…

如何设置多个图层层叠关系_如何玩转 XMind 中的多种思维结构?

熟悉 XMind 的用户都知道&#xff0c;XMind 支持多种思维结构&#xff0c;并且不同思维结构可以混用。每一个分支都可以是一个不同的结构&#xff0c;让你不受限制、自由地进行思维的发散和整理。这个是目前其他思维导图工具少有的。在 XMind 中&#xff0c;你可以用思维导图、…

python修改html表格,使用styles和css更改pandas dataframe html表python中...

这需要几个步骤&#xff1a;首先导入HTML并重新输入from IPython.display import HTMLimport re你可以通过to_html方法得到html pandas.df_html df.to_html()接下来,我们将为html表和我们要创建的样式生成随机标识符.random_id id%d % np.random.choice(np.arange(1000000))因…

PHP关于VC11,VC9,VC6以及Thread Safe和Non Thread Safe版本选择

2019独角兽企业重金招聘Python工程师标准>>> 这里是我在搭建php环境时收集的资料供大家参考&#xff1a; 现在PHP官网上下载PHP安装包都有VC11或VC9的字样&#xff0c;这是什么含义&#xff0c;我们应该下载哪种安装包更好呢&#xff1f;其实PHP官网给出了答案&…

Silverlight与WCF之间的通信(5)silverlight应用和wcf服务的发布方法

上一篇博文中有朋友问到关于silverlight程序发布的问题&#xff0c;上一篇写的是silverlight访问host在console上的wcf&#xff0c;其实关于wcf和silverlihgt通信的问题有好几种方式&#xff0c;这里列举了一下 客户端和服务端采用http协议通信&#xff08;分两种&#xff0c;同…

小程序引用其他页面js_来聊聊小程序页面之间如何通信

小程序页面之间如何通信?首先将通信的模型列举出来, 分为以下几种兄弟页面间通信父路径页面向子路径页面通信子路径页面向父路径页面通信通信的方式localStorage 本地存储globalData 全局对象eventBus 发布订阅PageModel 缓存整个pageModel至globalDataLocalStorage利用onShow…

用碧海潮声制作的宋体(雅黑宋体)替换Windows7原生的火柴棍式的宋体

第一眼见到Windows7的时候&#xff0c;界面真的非常漂亮&#xff0c;但是当查看文件属性的时候&#xff0c;里面宋体出奇的难看&#xff0c;网上有很多在XP里替换宋体的方法&#xff0c;但是到了Windows7里就不那么好用了&#xff0c;经过多次查找相关方法&#xff0c;终于使用…

json动画_three.js动画(四)

ThreeJS的动画系列分为&#xff1a;基础动画、相机控制、变形动画、用骨骼和蒙皮制作动画以及使用外部模型创建动画。用骨骼和蒙皮制作动画用骨骼来做动画时&#xff0c;移动一下骨骼&#xff0c;Three.js必须决定如何相应地迁移附着在骨骼上的皮肤&#xff0c;一起来看吧~~~举…

session很快失效_一口气说出 4 种分布式一致性 Session 实现方式,面试杠杠的~

前言公司有一个 Web 管理系统&#xff0c;使用 Tomcat 进行部署。由于是后台管理系统&#xff0c;所有的网页都需要登录授权之后才能进行相应的操作。起初这个系统的用的人也不多&#xff0c;为了节省资源&#xff0c;这个系统仅仅只是单机部署。后来随着用的人越来越多&#x…

定义动画名字html,CSS3 animation-name属性怎么用?

css3 animation-name属性是用来检索或设置对象所应用的动画名称&#xff0c;必须与keyframes配合使用&#xff0c;因为动画名称由keyframes定义 &#xff1b;如果有多个属性值&#xff0c;可以用逗号进行分隔。css3 animation-name属性作用&#xff1a;animation-name 属性为 k…

线程中如何使用对象_多线程中如何使用gdb精确定位死锁问题

在多线程开发过程中很多人应该都会遇到死锁问题&#xff0c;死锁问题也是面试过程中经常被问到的问题&#xff0c;这里介绍在c中如何使用gdbpython脚本调试死锁问题&#xff0c;以及如何在程序运行过程中检测死锁。首先介绍什么是死锁&#xff0c;看下维基百科中的定义&#xf…

html中如何把两行合并单元格,css合并两列单元格内容

用纯DIVCSS做一个两行两列的表格&#xff0c;但第二列中两行怎么在html里把一行中的两列合并世界最不可以相信的话,就是从女人嘴里说出的话〃如上图&#xff0c;怎么做出上面图中的效果&#xff0c;分享大神详解CSS表格单元格占两行可以参考以下的代码&#xff1a; 单元格占两行…

ASP.NET MVC 实现二级域名(泛域名)

自从微软发布 ASP.NET MVC 和routing engine (System.Web.Routing)以来&#xff0c;就设法让我们明白你完全能控制URL和routing&#xff0c;只要与你的application path相结合进行扩展&#xff0c;任何问题都迎刃而解。如果你需要在所处的域或者子域处理数据标记的话&#xff0…

list和tuple

2019独角兽企业重金招聘Python工程师标准>>> list Python内置的一种数据类型是列表&#xff1a;list。list是一种有序的集合&#xff0c;可以随时添加和删除其中的元素。 比如&#xff0c;列出班里所有同学的名字&#xff0c;就可以用一个list表示&#xff1a; >…

springboot数据源不正确_Spring MVC 到 Spring Boot 的简化之路

Spring全家桶笔记&#xff1a;SpringSpring BootSpring CloudSpring MVC​shimo.im01 背景从Servlet技术到Spring和Spring MVC&#xff0c;开发Web应用变得越来越简捷。但是Spring和Spring MVC的众多配置有时却让人望而却步&#xff0c;相信有过Spring MVC开发经验的朋友能深刻…

MapXtreme 包含所有自带坐标系一览

CoordSys 对象包含关于 X 和 Y 坐标如何与其在 Earth 上的位置相关联的基本信息。 每个 Geometry 或 Map 对象都有一个关联的坐标系。 CoordSys 对象包含对坐标系的详细说明。 CoordSysFactory 类提供了各种用于创建不同 CoordSys 对象的方法。 所有 CoordSys 对象都是只读的&a…