Spring Boot自定义错误视图
Spring Boot缺省错误视图解析器
Web应用在处理请求的过程中发生错误是非常常见的情况,SpringBoot中为我们实现了一个错误视图解析器(DefaultErrorViewResolver)。它基于一些常见的约定,尝试根据HTTP错误状态码解析出错误处理视图。它会在目录/error下针对提供的HTTP错误状态码搜索模板或者静态资源,比如,给定了HTTP状态码404,它会尝试搜索如下模板或者静态资源:
- /<templates>/error/404.<ext> - 这里<templates>表示所配置的模板所在目录,<ext>表示所用的模板的文件名
- /<static>/error/404.html - 这里<static>表示静态资源文件所在路径、
- /<templates>/error/4xx.<ext>
- /<static>/error/4xx.html
如果找不到就用默认的白标错误视图,如下图所示:
因此,为了给用户最佳的使用体验,404等常见错误需要我们自定义页面来处理。以下是几种自定义错误页面的方式。
方式1. 定义静态的错误页面
在 resources 下的 static 目录下,新建 error 目录,在其中新建各种静态错误页面,如 404、500,也可以模糊处理,如4xx、5xx 等,当程序运行出错时,会自动根据错误代码(如500)找到相应的错误页面(如/static/error/500.html),给予展示。
方式2. 定义动态的错误页面(有采用模板引擎)
在有使用模板的情况下,SpringBoot缺省的错误视图解析器也会在/<templates>/error下搜索错误展示视图。我们可以使用项目中的视图模板引擎在错误页面来定制展示我们的错误消息。
1) 在 resources 下的 templates 目录下,新建 error 目录,在其中新建各种静态错误页面,如 404、500,也可以模糊处理,如4xx、5xx 等(与方式1一致)+
在模板引擎的支持下可以取到错误的一些信息,并定制化显示在页面上,如下(freemarker模板):
错误信息定制:
- timestamp:时间戳
- status:状态码
- error:错误提示
- exception:异常对象
- trace:跟踪流程日志,404状态下无
- message:异常消息
- path:请求路径
方式3. 自定义实现错误视图解析,统一错误处理
如果不想要使用缺省的错误处理视图解析器,想要定制一些自己的东西(比如说:错误引导信息等),按照官方文档的建议我们可以自定义实现错误视图解析接口来处理。
下面就是通过实现错误视图解析接口ErrorViewResolver,将4xx、5xx 的错误页面集中在一个自定义视图上:
1)实现 ErrorViewResolver 接口
package com.hongyang.admin.web; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import java.util.Map; /** * 实现自定义的错误视图解析器 */ @Component public class AdminErrorViewResolver implements ErrorViewResolver { /** * 实现ErrorViewResolver约定方法, * 返回统一的错误视图. * @param request * @param status * @param model * @return */ @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { return new ModelAndView("/error/index", model); } }
2)完成错误视图,在templates/error下添加index.ftlh视图(freemarker模板)
<!DOCTYPE html> <html> <head > <link href="/content/public/images/logo-small.png" rel="shortcut icon" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>${status}</title> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="format-detection" content="telephone=no"> <style> body { position: fixed; z-index: 10; top: 0; left: 0; right: 0; bottom: 0; width: 100%; padding: 0; margin: 0px; font-size: 14px; background: #fff; word-wrap: break-word; } p { padding: 0 15px; } .btn { border: 0px; color: #fff; cursor: pointer; text-align: center; background-color: #ff7a5f\0; box-shadow: #cccccc 0 2px 15px 0; -webkit-box-shadow: 0 2px 7px 0 rgba(0,0,0,0.2); background: radial-gradient(circle at 300% 50%, rgb(255, 195, 114) 50%, rgb(255, 105, 90) 100%); transition: all .2s ease-out,box-shadow .2s ease-out; } .btn:hover { color: #FFFFFF; transform: scale(1.1); } .btn:focus { outline: none; } .container { margin: 8% auto; } .container p { margin: 35px auto; text-align: center; } .container img { width: 20%; } .container .font { color: #848484; } .container .btn-back { width: 180px; height: 35px; border-radius: 6px; } #container-info { display: none; position: fixed; z-index: 11; top: 5%; left: 0; right: 0; margin: 0 auto; width: 65%; height: 85%; overflow: hidden; border-radius: 4px; border: 1px solid #f1986e; background-color: #fff; box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgb(242, 154, 110); } #container-info .btn-close { position: absolute; right: 5px; top: 5px; width: 25px; height: 25px; line-height: 25px; font-size: 22px; padding: 2px; border-radius: 50%; text-align: center; } #container-info p { font-size: 12px; line-height: 20px; } .show { display: block !important; } .cor-r { color: red; } @media (max-width: 767px) { .container img { width: 50%; } #container-info { width: 85%; } } .panel-heading { height: 32px; line-height: 32px; padding: 5px 15px; } .panel-body { height: calc(85vh - 40px); overflow: auto; } .panel-orange .panel-heading { background-color: #ffa0681f; color: #ff6f5c; } </style> </head> <body> <form id="form1" > <div class="container"> <p><img src="/content/public/images/error_${status}.png"/></p> <p class="font">${error},<a onclick="errorDetail(true)" href="javascript: void(0)">点击查看明细</a>!</p> <p><button type="button" class="btn btn-back" id="btnBack" >返回</button></p> </div> <div id="container-info"> <span class="btn btn-close" onclick="errorDetail(false)">×</span> <div class="panel panel-orange"> <div class="panel-heading"> 错误说明 </div> <div class="panel-body"> <#if path??> <p><b>请求的URL:</b>${path}</p> </#if> <#if message??> <p><b>异常信息:</b><span class="cor-r">${message}</span></p> </#if> <#if trace??> <p><b>StackTrace:</b><br>${trace}</p> </#if> </div> </div> </div> <script type="text/javascript"> window.onload = function () { var btn = document.getElementById("btnBack"); btn.onclick = function () { var url = document.referrer; if (url.indexOf("home/main") > 0) { window.parent.tabDelete(); return; } window.history.back(-1); } } function errorDetail(isShow) { var con = document.getElementById("container-info"); con.className = isShow ? "show" : ""; // 兼容IE8 } </script> </form> </body> </html>
3)最后效果
错误页面的展示优先级
1、精确大于模糊
2、动态大于静态