Spring Boot(五)—— 默认的错误处理机制

一、默认处理机制

默认效果:

1、浏览器访问:返回一个默认的错误页面

image-20200224125600523

浏览器请求头:

image-20200224130001851

2、其他客户端,默认相应json数据

image-20200224130054181

请求头:

image-20200224130113923

1、原理

ErrorMvcAutoConfiguration,错误处理的自动配置

给容器添加了一下组件:

(1). ErrorPageCustomizer(错误页面定制器)

 @Override
 public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
             ErrorPage errorPage = new ErrorPage(
     this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
             errorPageRegistry.addErrorPages(errorPage);
 }
 ====getPath()方法
 public String getPath() {
     return this.path;
 }
 @Value("${error.path:/error}")
 private String path = "/error"; // 系统出错以后来到error请求进行处理;(web.xml注册的错误页面规则)

(2). BasicErrorController:处理默认 /error 请求,能自适应返回页面或json数据

 @Controller
 @RequestMapping("${server.error.path:${error.path:/error}}")
 public class BasicErrorController extends AbstractErrorController {
     
     // 根据客户端的请求头,选择相应的数据:html或json
     @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) // 产生html数据,浏览器
     public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
         HttpStatus status = getStatus(request);
         Map<String, Object> model = Collections
                 .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
         response.setStatus(status.value());
         ModelAndView modelAndView = resolveErrorView(request, response, status, model);
         return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
     }
 
     @RequestMapping     // 产生json数据,其他客户端
     public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
         HttpStatus status = getStatus(request);
         if (status == HttpStatus.NO_CONTENT) {
             return new ResponseEntity<>(status);
         }
         Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
         return new ResponseEntity<>(body, status);
     }
 }

(3). 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())) {
             modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
         }
         return modelAndView;
     }
 
     private ModelAndView resolve(String viewName, Map<String, Object> model) {
         // SpringBoot默认可以找到一个页面,error/status: error/404,error/500
         String errorViewName = "error/" + viewName;
         // 模板引擎可以解析这个页面地址就用模板引擎解析
         TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
                 this.applicationContext);
         if (provider != null) {
             // 模板引擎可用的情况下返回到errorViewName指定的视图地址
             return new ModelAndView(errorViewName, model);
         }
         // 模板引擎不可用,就在静态资源路径下找errorViewName对应的页面
         return resolveResource(errorViewName, model);
     }

(4). DefaultErrorAttribute,定制错误响应信息

 // 帮我们在页面中共享信息  
     @Override
     public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
         Map<String, Object> errorAttributes = new LinkedHashMap<>();
         errorAttributes.put("timestamp", new Date());
         addStatus(errorAttributes, webRequest);
         addErrorDetails(errorAttributes, webRequest, includeStackTrace);
         addPath(errorAttributes, webRequest);
         return errorAttributes;
     }

2、默认实现步骤

一旦系统出现4xx或5xx的错误;ErrorPageCustomizer就会生效(定制错误的相应规则);就会来到/error请求;就会被BasicErrorController处理:

(1). 响应页面,errorHtml(…)方法

 // BasicErrorController.java -> errorHtml(...) 方法中
 ...
 ModelAndView modelAndView = resolveErrorView(request, response, status, model);
 ...
 =============
 protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
             Map<String, Object> model) {
         // 所有的ErrorViewResolver得到ModelAndView,都是由DefaultErrorViewResolver解析的
         for (ErrorViewResolver resolver : this.errorViewResolvers) {
             ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
             if (modelAndView != null) {
                 return modelAndView;
             }
         }
         return null;
 }

(2). 响应json数据,error(…)方法

二、定制错误响应

1、如何定制响应错误的页面

  • 有模板引擎的情况下;error/状态码【将错误页面命名为 错误状态码.html 放在模板引擎文件夹下的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;

    • 我们可以使用 4xx 或 5xx 作为文件名来匹配这种类型的所有错误,精确的状态码.html 优先。
  • 页面能获取到的信息(由DefaultErrorAttribute获取)

    • timestamp:时间戳

    • status:状态码

      • error:错误提示
      • exception:异常对象
      • message:异常消息
      • errors:JSR303数据校验的错误所在
  • 没有模板引擎的情况下(模板引擎下找不到/error文件夹);在静态资源路径下找,但是获取不到动态信息(timestamp,status…)

  • 以上都没有,使用SpringBoot默认警告页面

2、如何定制响应错误的json数据

SpringBoot 2.x 中需配置

 # 页面中 [[${exception}]] 可以获取到数据
 server.error.include-exception=true
  • (1). 自定义异常处理&返回定制json数据:(缺陷:浏览器访问也将返回json数据)

     @ControllerAdvice
     public class MyExceptionHandler {
         @ResponseBody
         @ExceptionHandler
         public Map<String,Object> handlerException(Exception e){
             Map<String,Object> map = new HashMap<>();
             map.put("code","用户名不存在");
             map.put("message",e.getMessage());
             return map;
         }
     }
     // 无法自适应相应。浏览器和其他客户端都返回json数据
    
  • (2). 转发到/error进行自适应响应效果处理(缺陷:页面和json无法显示自定义数据)

         @ExceptionHandler(UserNotExistException.class)
         public String handlerException(Exception e, HttpServletRequest request){
             Map<String,Object> map = new HashMap<>();
             map.put("code","用户名不存在");
             map.put("message",e.getMessage());
             // 转发前,先传入自身错误状态码,根据自定义错误类的需求
             request.setAttribute("javax.servlet.error.status_code",500);
             // 转发到 /error能自适响应数据
             return "forward:/error";
         }
    
  • (3). 实现浏览器或其他客户端访问,相应对应页面,并显示自定义信息

 BasicErrorController类
 ===
         @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
     public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
         ...
         Map<String, Object> model = Collections
                 .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
         ...
         return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
     }
 ===
     @RequestMapping
     public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
 ...
         Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
         return new ResponseEntity<>(body, status);
     }

由 getErrorAttributes() 方法获取到错误信息。

SpringBoot自动配置中默认使用实现了ErrorController接口 AbstractErrorController 抽象类书写该方法。

所以我们有三种方法写自定义错误信息:

  1. 写AbstractErrorController 的子类,放到容器中

  2. 写实现了ErrorController接口的实现类,放到容器中(选)

  3. 写 DefaultErrorAttributes 的子类,它还可以获取到默认的错误信息(选)

     ErrorMvcAutoConfiguration类  
         @Bean
         @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
         public DefaultErrorAttributes errorAttributes() {
             return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
         }
     // 如果没有 ErrorAttributes 类使用默认,我们写 ErrorAttributes 的实现类
    
     // 给容器添加自定义的 ErrorAttribute
     @Component
     public class MyErrorAttributes extends DefaultErrorAttributes {
     
         public MyErrorAttributes() {
             super(true);
         }
     
         @Override
         public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
             Map<String,Object> map = super.getErrorAttributes(webRequest,includeStackTrace);
             map.put("code23","user not exist 23");
             // errorMap 这个request域中的key,来自自定义异常类中传入的值
             Map<String,Object> extMap = (Map<String, Object>) webRequest.getAttribute("errorMap",0);
             map.put("ex",extMap);
             return map;
         }
     }
    

    ps: 浏览器访问返回的错误页面中添加的错误信息对象,必须是map中定义的key,否则为空。

总结: 为在其他客户端访问时,返回自定义的json数据,切保证返回页面也可以拿到自定义数据。

写自定义异常类 -> 异常类中写map1存入错误信息键值对,并将map1数据存入request域中 -> 转发到 /error,可以自适应返回json或页面 -> 写 DefaultErrorAttributes 的子类,这样也可以拿到默认返回的数据 - > 拿到默认数据的map2,将request域中的map1取出存入到map2中(key自定义)- > 错误页面可取默认错误数据(默认对象)、map2数据(map2的key)、map1 数据(map2的key . map1的key)(有点绕…)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值