本例介绍SpringBoot错误处理机制
错误处理现象
新建一个SpringBoot Web项目,在浏览器中随便输入一个错误地址进行访问,如:http://localhost:8081/test/aa,会出现一个错误页面
浏览器页面错误:
如果用PostMan请求错误地址,得到一个Json错误
Json错误:
错误处理原理
可以参考ErrorMvcAutoConfiguration,错误处理的自动配置
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
1、DefaultErrorAttributes: 错误请求中有的属性
1 @Override 2 public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { 3 Map<String, Object> errorAttributes = new LinkedHashMap<>(); 4 errorAttributes.put("timestamp", new Date()); 5 addStatus(errorAttributes, webRequest); 6 addErrorDetails(errorAttributes, webRequest, includeStackTrace); 7 addPath(errorAttributes, webRequest); 8 return errorAttributes; 9 }
2、BasicErrorController: 处理默认/error请求
1 // 产生error界面 2 @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) 3 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { 4 HttpStatus status = getStatus(request); 5 Map<String, Object> model = Collections 6 // getErrorAttributes 获取错误内容 7 .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); 8 response.setStatus(status.value()); 9 10 // 去哪个错误界面 11 ModelAndView modelAndView = resolveErrorView(request, response, status, model); 12 return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); 13 } 14 15 // 产生错误json数据 16 @RequestMapping 17 public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { 18 Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); 19 HttpStatus status = getStatus(request); 20 return new ResponseEntity<>(body, status); 21 }
3、ErrorPageCustomizer: 系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
1 /** 2 * {@link WebServerFactoryCustomizer} that configures the server's error pages. 3 */ 4 private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { 5 6 private final ServerProperties properties; 7 8 private final DispatcherServletPath dispatcherServletPath; 9 10 protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) { 11 this.properties = properties; 12 this.dispatcherServletPath = dispatcherServletPath; 13 } 14 15 // 注入错误页面 16 @Override 17 public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { 18 ErrorPage errorPage = new ErrorPage( 19 this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())); 20 errorPageRegistry.addErrorPages(errorPage); 21 } 22 23 @Override 24 public int getOrder() { 25 return 0; 26 } 27 28 }
// 错误路径配置
1 @Value("${error.path:/error}") 2 private String path = "/error";
4、DefaultErrorViewResolver: 响应页面去哪个页面是由DefaultErrorViewResolver解析得到的;
@Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { // status.series() 状态码 modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { //默认SpringBoot可以去找到一个页面? error/404 String errorViewName = "error/" + viewName; //模板引擎可以解析这个页面地址就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { //模板引擎可用的情况下返回到errorViewName指定的视图地址 return new ModelAndView(errorViewName, model); } //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html return resolveResource(errorViewName, model); }
定制错误响应
1、定制错误的页面
a、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹下】,发生此状态码的错误就会来到 对应的页面; 可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息:
-
-
- timestamp:时间戳
- status:状态码
- error:错误提示
- exception:异常对象
- message:异常消息
- errors:JSR303数据校验的错误都在这里
-
404.html如下:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>404</title> 6 </head> 7 <body> 8 <h3> 404 </h3> 9 <h3> [[${status}]]</h3> 10 <h3> [[${timestamp}]]</h3> 11 </body> 12 </html>
b、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
c、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
2、定制错误的json数据
a、自定义异常处理&返回定制json数据;没有自适应效果
1 package com.test.springboot.controller; 2 3 import com.test.springboot.exception.UserNotExistException; 4 import org.springframework.web.bind.annotation.ControllerAdvice; 5 import org.springframework.web.bind.annotation.ExceptionHandler; 6 import org.springframework.web.bind.annotation.ResponseBody; 7 8 import javax.servlet.http.HttpServletRequest; 9 import java.util.HashMap; 10 import java.util.Map; 11 12 @ControllerAdvice 13 public class MyExceptionHandler { 14 15 // 浏览器和客户端返回的都是json 16 @ResponseBody 17 @ExceptionHandler(UserNotExistException.class) 18 public Map<String, Object> handlerException(Exception e){ 19 HashMap<String, Object> map = new HashMap<>(); 20 map.put("code", "not.exist"); 21 map.put("message", e.getMessage()); 22 return map; 23 } 24 }
b、转发到/error进行自适应响应效果处理
1 // 页面请求 返回错误页面 2 // 非页面请求 返回json数据 3 @ExceptionHandler(UserNotExistException.class) 4 public String handlerException(Exception e, HttpServletRequest request){ 5 // 设置自己的错误状态码 4xx 5xx 6 // 否则就不会进入定制错误页面的解析流程 7 /** 8 * Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); 9 */ 10 request.setAttribute("javax.servlet.error.status_code", 500); 11 12 HashMap<String, Object> map = new HashMap<>(); 13 map.put("code", "not.exist"); 14 map.put("message", e.getMessage()); 15 // 转发的error请求 16 return "forward:/error"; 17 }
c、将定制数据携带出去
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
方法1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
方法2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到; 容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
方法2实现如下:
1 // 给容器中添加自定义的ErrorAttributes 2 @Component 3 public class MyErrorAttributes extends DefaultErrorAttributes { 4 5 // 返回值的map就是页面和json能获取的所有字段 6 @Override 7 public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { 8 Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); 9 // 加入公司字段 10 map.put("company", "test.com"); 11 // 加入请求域request中的数据 12 map.put("ext", webRequest.getAttribute("ext", 0)); 13 14 return map; 15 } 16 }
效果如下(如果是页面,直接用${ext.code }获取即可):