SpringBoot与web开发(五) ---- SpringBoot 错误处理机制

SpringBoot 错误处理机制

Page content

SpringBoot 错误处理

SpringBoot 默认错误机制,返回一个错误页面和 json数据

  • 浏览器

错误页面

请求头

  • 其它客户端

404 json数据

其它客户端请求头

1. 原理

源码 ErrorMvcAutoConfiguration

其中有几个重要的组件:

  • DefaultErrorAttributes : 共享信息

    • timestamp 时间
    • status 状态码
    • error 错误提示
    • exception 异常对象
    • message 异常信息
    • errors JSR303 数据校验的信息
  • 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) {
     String errorViewName = "error/" + viewName;
    //使用模板引擎
     TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
           this.applicationContext);
     if (provider != null) {
        return new ModelAndView(errorViewName, model);
     }
     //没有模板引擎 自动在静态资源下面去找
     return resolveResource(errorViewName, model);
    }	
    
  • ErrorPageCustomizer : 自定义

  • BasicErrorController : 处理 /error 请求

    @Controller
    @RequestMapping("${server.error.path:${error.path:/error}}")
    public class BasicErrorController extends AbstractErrorController {
      //html  浏览器
    	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    	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);
    	}
      //json  其他客户端
    	@RequestMapping
    	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    		Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
    		HttpStatus status = getStatus(request);
    		return new ResponseEntity<>(body, status);
    	}
    

2. 自定义

  • 自定义页面

    1. 有模板引擎,将404页面放在templates/error下面,(4xx)都可以找到 , 同理5xx也是可以的(首先是精确匹配)。
    2. 没有模板引擎,static 目录下创建error目录以及错误页面。
  • 自定义 JSON 数据

    • 以下测试的controller

      /**
       * 测试
       * @param user 测试用户名:aa
       * @return Exception or String
       */
      @RequestMapping("/hello")
      @ResponseBody
      public String hello(@RequestParam("user") String user) {
          if (user.equals("aa")) {
              throw new UserNotException();
          }
          return "hello";
      }
      
      
    1. 统一显示json

      /**
      * @program: spring-boot-restful-crud
      * @description: 异常处理器
      * @author: YuanChangYue
      * @create: 2019-08-19 12:53
      */
      @ControllerAdvice
      public class MyExceptionHandler {
      
       /**
        * 处理UserNotException
        * 同意返回的是json数据 而不是分开显示为页面或者json
        * @return 处理信息
        */
       @ResponseBody
       @ExceptionHandler(UserNotException.class)
       public Map<String, Object> handlerException(Exception e) {
           Map<String, Object> map = new HashMap<>();
           map.put("code", "user not exist");
           map.put("message", e.getMessage());
           return map;
       }
      }
      
      /**
      * @program: spring-boot-restful-crud
      * @description: 用户不存在异常类
      * @author: YuanChangYue
      * @create: 2019-08-19 12:44
      */
      public class UserNotException extends RuntimeException {
       public UserNotException() {
           super("用户不存在");
       }
      }
      
      

    同意显示

    同意显示

    1. 页面和json分开 (通过转发到error)

      /**
      * @program: spring-boot-restful-crud
      * @description: 异常处理器
      * @author: YuanChangYue
      * @create: 2019-08-19 12:53
      */
      @ControllerAdvice
      public class MyExceptionHandler {
      
      //    /**
      //     * 处理UserNotException
      //     *
      //     * @return 处理信息
      //     */
      //    @ResponseBody
      //    @ExceptionHandler(UserNotException.class)
      //    public Map<String, Object> handlerException(Exception e) {
      //        Map<String, Object> map = new HashMap<>();
      //        map.put("code", "user not exist");
      //        map.put("message", e.getMessage());
      //        return map;
      //    }
      
      /**
        * 处理UserNotException
        * 注意:一定要设置错误代码
        * 根据:org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#getStatus(javax.servlet.http.HttpServletRequest)
        * 关键点:request.setAttribute("javax.servlet.error.status_code", 500);
        *
        * @return 处理信息
        */
       @ResponseBody
       @ExceptionHandler(UserNotException.class)
       public String handlerException(Exception e, HttpServletRequest request) {
           Map<String, Object> map = new HashMap<>();
           //传入错误代码 4xx 5xx
           //Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
           request.setAttribute("javax.servlet.error.status_code", 500);
           map.put("code", "user not exist");
           map.put("message", e.getMessage());
           return "forward:/error";
       }
      }
      
      

    页面显示

    post

    1. 将我们的定制数据传递出去

      当出现error时候,会来到/error请求中,这样就会被BasicErrorController类处理,查看这个类发现,相应的响应数据是由getErrorAttributes(在BasicErrorController继承的父类AbstractErrorControllerErrorController.class实现类))方法的得到的。再看ErrorMvcAutoConfiguration自动装载BasicErrorController

       @Bean
        //当没有ErrorController.class就会加载BasicErrorController到容器中
        @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
        public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);}
      
      • 这样就可以来编写一个BasicErrorController的实现类,放在容器中。
      • BasicErrorController最终会在ErrorAttributes中取出响应数据,所以 我们可以编写自己的ErrorAttribute.

            
        /**
        * @program: spring-boot-restful-crud
        * @description: 自定义ErrorAttributes
        * @author: YuanChangYue
        * @create: 2019-08-19 15:02
        */
        public class MyErrorAtrributes extends DefaultErrorAttributes {
                  
          @Override
          public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
              Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
              errorAttributes.put("owner", "ChangYue");
              return errorAttributes;
          } 
        }
        

      自定义attribute

​ 将自定义异常处理类需要传递的数据传递到自定义ErrorAttributes中,并添加进去

  /**
   * @program: spring-boot-restful-crud
   * @description: 自定义ErrorAttributes
   * @author: YuanChangYue
   * @create: 2019-08-19 15:02
   */
  @Component
  public class MyErrorAttributes extends DefaultErrorAttributes {
  
      /**
       * @return 页面和json获取的字段
       */
      @Override
      public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
          Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
          errorAttributes.put("owner", "ChangYue");
          //自定义需要的传递的数据
          Map<String, Object> exc = (Map<String, Object>) webRequest.getAttribute("exc", 0);
          errorAttributes.put("exc", exc);
          return errorAttributes;
      }
  
  }
  
  /**
   * @program: spring-boot-restful-crud
   * @description: 异常处理器
   * @author: YuanChangYue
   * @create: 2019-08-19 12:53
   */
  @ControllerAdvice
  public class MyExceptionHandler {
  
      /**
       * 处理UserNotException
       * 注意:一定要设置错误代码
       * 根据:org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#getStatus(javax.servlet.http.HttpServletRequest)
       * 关键点:request.setAttribute("javax.servlet.error.status_code", 500);
       *
       * @return 处理信息
       */
      @ExceptionHandler(UserNotException.class)
      public String handlerException(Exception e, HttpServletRequest request) {
          Map<String, Object> map = new HashMap<>();
          //传入错误代码 4xx 5xx
          //Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
          request.setAttribute("javax.servlet.error.status_code", 500);
          map.put("code", "user not exist");
          map.put("message", e.getMessage());
          request.setAttribute("exc", map);
          return "forward:/error";
      }
  }

自定义attribute2

同时在错误页面中也可以显示出来

自定义attribute2_html