H__D  

  本例介绍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>
View Code  

    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 }获取即可):

        

 

 

 

 

 

 

 

posted on 2020-03-01 03:04  H__D  阅读(395)  评论(0编辑  收藏  举报