目录
默认异常处理
BasicErrorController
返回页面+数据:errorHtml()
自定义统一异常处理
静态异常处理页面
动态异常处理(模板引擎)页面
自定义异常数据
SpringBoot版本:2.1.1
默认情况下,Spring Boot异常页面显示如下:
下面第一句:此应用程序没有针对/error的显式映射。
Spring Boot提供了一个处理所有异常的映射 “ /error ”,并将其注册为容器中的“全局”异常页面,位于org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController。
对于计算机客户端(比如postman),它会生成一个JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。
对于浏览器客户端,有一个默认的异常视图,它以HTML格式呈现相同的数据,就是上面的默认异常页面。
可以看到默认处理就是“/error”请求。有两个请求映射方法errorHtml()和error(),第一个方法是返回页面+数据,第二个方法是放回JSON。
最终是调用DefaultErrorViewResolver的resolveErrorView方法创建ModelAndView 对象,如果返回的ModelAndView 为空,则显示的就是默认的异常页面。
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
//request.getAttribute("javax.servlet.error.status_code");如果为空则返回500
HttpStatus status = getStatus(request);
//得到异常信息,包括timestamp,status,error,message,path;
//具体可以到DefaultErrorAttributes的getErrorAttributes方法查看
Map model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
//设置响应状态
response.setStatus(status.value());
//创建ModelAndView
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
看下DefaultErrorViewResolver的类注释。
大概意思如下:
ErrorViewResolver的默认实现,将使用状态代码和状态序列在“/error”目录下搜索模板和静态资源。
例如,HTTP 404将搜索(按特定顺序):
首先以异常响应码作为视图名分别去查找错误模板和静态页面,如果为空,再以 4xx 或者 5xx 作为视图名再去分别查找错误模板或者静态页面。
Spring Boot支持的模板有:
看下面的resolveErrorView()方法,逻辑跟上面注释说的顺序是一样的。
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map model) {
//传入状态码和错误信息,这里是500
//第一次根据状态码先找模板,如果没找到,再找HTML页面
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
//第二次根据4xx或5xx找,同样先找模板,在找HTML页面
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map model) {
String errorViewName = "error/" + viewName;
//找模板
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
//找静态资源
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map model) {
//再找html页面
//得到静态资源路径,循环
for (String location : this.resourceProperties.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
静态资源的四个默认路径,模板的默认路径是在org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties定义的,即默认的前缀后缀,有配置项可以更改。
知道了Spring Boot异常处理的逻辑,现在可以自定义异常页面了。分为两种,第一种 是使用 HTTP 响应码来命名页面,例如 404.html、405.html、500.html ....,另一种就是直接定义一个 4xx.html,表示400-499 的状态都显示这个异常页面,5xx.html 表示 500-599 的状态显示这个异常页面。
在resources下新建static目录,再建一层error目录,然后新建404.html和5xx.html。一样的建一个演示下。http状态码自行去了解吧。
两个HTML的内容就不展示,就在下面,简单的输出。
效果如下:
我用的是thymelef,导入依赖:
org.springframework.boot
spring-boot-starter-thymeleaf
添加配置项:spring.thymelef.cache=false //开发时关闭缓存,不然没法看到实时页面
在resources下新建templates目录,同样再建一层error目录,然后新建4xx.html和5xx.html。一样的建一个演示下。
4xx.html文件内容,5xx.html内容一样,只是h1里面内容不一样:
Insert title here
4xx templates
path
error
message
timestamp
status
效果如下:
model中的异常数据是在org.springframework.boot.web.servlet.error.DefaultErrorAttributes的getErrorAttributes()方法中定义的,方便点,你可以继承DefaultErrorAttributes,重写该方法,异常数据在父类已经定义好了,只要拿来用。
如下:
import java.util.Map;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
if ((int)errorAttributes.get("status")==500) {
errorAttributes.replace("message", "服务器内部错误");
}
return errorAttributes;
}
}
当然还有其它方法,ErrorPageRegistrar接口,添加自定义的ErrorPage;@ControllerAdvice注解配合@ExceptionHandler(value=Exception.class)注解。
个人觉得上面几种方法已经能满足基本需要了。