SpringBoot2.1.4错误处理机制
前面一片已经介绍了springboot错误处理的机制,其实从整个分析过程中我们已经大概知道如何定制了。
1、错误页面自定义
springboot有个默认的错误页面,但是开发时错误页面肯定是自己定义的。那该如何定义?
在DefaultErrorViewResolver类中有下面几个方法,
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 定义视图名,这里我们可以确定视图名:error/错误码,例如:error/404,
String errorViewName = "error/" + viewName;
// 这里结合上面的errorViewName,其实就是在template目录下的error目录进行查找
// 我们默认情况下是没有error目录,这里的provide最终值为null,代码较多就不一一展示,有兴趣的可以跟下去
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
// 根据判定,这里会接着调用下面的resolveResource方法
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
// getStaticLocations()获取的是静态资源路径:"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/"
String[] var3 = this.resourceProperties.getStaticLocations();
int var4 = var3.length;
// 遍历上面的4个静态资源路径
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
try {
Resource resource = this.applicationContext.getResource(location);
// 创建resource对象,例如error/404.html
resource = resource.createRelative(viewName + ".html");
// 查找在对应静态资源目录下是否有上面的这个资源对象,有就创建视图对象
if (resource.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception var8) {
;
}
}
// 都没找到就返回null,默认情况下是不存在error目录的,所以这里最终返回null
return null;
}
在解析错误视图界面时,会依次去这几个目录:template/、 classpath:/META-INF/resources/ 、 classpath:/resources/、 classpath:/static/ 、classpath:/public/,在这些目录下的error目录里找文件名是错误状态码的页面文件(404、500…)。
所以我们可以在这些目录下创建error目录,在里面创建HTML页面,但是在template下面的页面才能被thymeleaf模板引擎识别。
现在再去访问不存在路径时效果如下,因为是404错误
但是,错误状态码是很多的,不可能将所有的页面全部写出来,在DefaultErrorViewResolver类中有下面一段静态代码:
static {
Map<Series, String> views = new EnumMap(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
向map中添加了两个数据"4xx"、“5xx”,这个表示4开头或者5开头的错误,并且看到下面的:
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// 按状态码精确查找
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
// 若精确查找未找到再来模糊查找
// status.series()方法可以自己看一下,就是在获取错误码的首位
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
上面注释写的很清楚了,springboot首先按错误精确查找,若为找到再以错误码首位模糊查找,所以我们也可以将错误页面文件定义成 4xx.html 或者 5xx.html。
2、错误数据
2.1 默认错误数据
页面搞定了,那默认的错误数据有哪些呢?响应浏览器或者移动端错误请求的两个方法中都同时用到了getErrorAttributes方法,这个方法就是用来获取错误数据的。
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
这个方法是属于DefaultErrorAttributes类的,用来定义默认错误属性。
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
从这个方法及其里面调用其他几个方法跟踪可以知道有如下错误属性:
- timestamp:时间戳
- status:错误码
- error:错误名
- exception:异常类型
- message:异常信息
- trace:错误栈
- path:错误路径
2.2 自定义错误数据
下面模拟一个场景,发起一个请求,然后触发一个自定义异常,自己添加一些错误信息,在页面上显示出来。
exception:
public class MyException extends RuntimeException {
public MyException() {
super("发生异常了");
}
}
Controller:
@ResponseBody
@RequestMapping("/hello")
public String hello(@RequestParam("username") String username){
// 当username值为aaa是抛出异常
if(username.equals("aaa")){
throw new MyException();
}
return "hello";
}
为了处理异常还要定义个异常处理器,关键点1:注意设置状态码的注释:
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(MyException.class)
public String f(HttpServletRequest request){
// 这里要更改状态码,前面访问路径是没有问题的,所以状态码为200
// 若想要进入springboot错误处理流程,必须重设状态码
// 并且其key值为:javax.servlet.error.status_code
request.setAttribute("javax.servlet.error.status_code",500);
// 这里可以添加信息到request中,到后面的取出添加到map中
request.setAttribute("data","我的错误消息");
// 转发到 /error请求,交给springboot处理
return "forward:/error";
}
}
关键点2,因为我们的错误信都是在DefaultErrorAttributes类中的getErrorAttributes方法中获取的,若只是到上面步骤为止,那么在移动端将无法获取到添加的data,所以为止同时使用浏览器和移动端,我们还必须创建一个类继承DefaultErrorAttributes类,重写getErrorAttributes方法,在这里才是真正的添加自定义的数据。
MyErrorAttribute:
// 注意,给类必须添加到容器中,否则不生效
// 添加后将会覆盖原有的DefaultErrorAttributes,采用我们自己的MyErrorAtribute
@Component
public class MyErrorAtribute extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
// 获取包含错误信息的map
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
// 添加自己的错误数据
map.put("company","lr");
//获取转发过来时添加的数据
// 第二个参数表示在哪个域中获取,0:request,1:session
String data = (String)webRequest.getAttribute("data", 0);
map.put("data",data); // 添加到map
return map;
}
}
然后我们测试,浏览器端:
移动端:
这样,我们错误页面,错误消息自定义,浏览器和移动端适配都解决了。