SpringBoot(六) -- SpringBoot错误处理机制


一.SpringBoot中的默认的错误处理机制
  1.在SpringBootWeb开发中,当我们访问请求出现错误时,会返回一个默认的错误页面:

  2.在使用其他客户端访问的时候,则返回一个json数据:

  3.原理:可以参看原码ErrorMvcAutoConfiguration:
    (1)给容器中添加了以下组件
    DefaultErrorAttributes:帮我们在页面共享信息

 1     @Override
 2     public Map getErrorAttributes(RequestAttributes requestAttributes,
 3             boolean includeStackTrace) {
 4         Map errorAttributes = new LinkedHashMap();
 5         errorAttributes.put("timestamp", new Date());
 6         addStatus(errorAttributes, requestAttributes);
 7         addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
 8         addPath(errorAttributes, requestAttributes);
 9         return errorAttributes;
10     }
11 
12     private void addStatus(Map errorAttributes,
13             RequestAttributes requestAttributes) {
14         Integer status = getAttribute(requestAttributes,
15                 "javax.servlet.error.status_code");
16         if (status == null) {
17             errorAttributes.put("status", 999);
18             errorAttributes.put("error", "None");
19             return;
20         }
21         errorAttributes.put("status", status);
22         try {
23             errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
24         }
25         catch (Exception ex) {
26             // Unable to obtain a reason
27             errorAttributes.put("error", "Http Status " + status);
28         }
29     }

    --可获取到的属性
      timestamp:时间戳
      status:状态码
      error:错误提示
      exception:异常
      message:异常消息
      errors:JSR303数据校验的错误都在这
    BasicErrorController:处理默认的/eerror请求

 1     @RequestMapping(produces = "text/html")
 2     public ModelAndView errorHtml(HttpServletRequest request,
 3             HttpServletResponse response) {
 4         HttpStatus status = getStatus(request);
 5         Map model = Collections.unmodifiableMap(getErrorAttributes(
 6                 request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
 7         response.setStatus(status.value());
 8         ModelAndView modelAndView = resolveErrorView(request, response, status, model);
 9         return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
10     }
11 
12     @RequestMapping
13     @ResponseBody
14     public ResponseEntity> error(HttpServletRequest request) {
15         Map body = getErrorAttributes(request,
16                 isIncludeStackTrace(request, MediaType.ALL));
17         HttpStatus status = getStatus(request);
18         return new ResponseEntity>(body, status);
19     }  

    --将会存在两种情况,得到一个html页面或者一个JSON数据,在浏览器发起的请求中,可发现其请求头accept中标识着优先接收html页面:

    --当其他客户端发送请求时,可以发现其请求头没有标注优先接收html数据(使用了*/*)

     --浏览器发送的请求将来到错误的html页面,而其他客户端则会接收到一个错误的JSON数据.在浏览器发送错误请求时,使用了ModelAndView声明了当前错误页面的地址和页面内容.

 1     protected ModelAndView resolveErrorView(HttpServletRequest request,
 2             HttpServletResponse response, HttpStatus status, Map model) {
 3         for (ErrorViewResolver resolver : this.errorViewResolvers) {
 4             ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
 5             if (modelAndView != null) {
 6                 return modelAndView;
 7             }
 8         }
 9         return null;
10     }

    --在该方法中将所有的ErrorViewResolver都获取到,而后返回,而我们去哪个页面则是由ErrorViewResolver得到的.
    ErrorPageCustomizer:系统出现错误时,来到error请求进行处理
    DefaultErrorViewResolver:

 1 @Override
 2     public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
 3             Map model) {
 4         ModelAndView modelAndView = resolve(String.valueOf(status), model);
 5         if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
 6             modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
 7         }
 8         return modelAndView;
 9     }
10 
11     private ModelAndView resolve(String viewName, Map model) {
12         String errorViewName = "error/" + viewName;
13         TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
14                 .getProvider(errorViewName, this.applicationContext);
15         if (provider != null) {
16             return new ModelAndView(errorViewName, model);
17         }
18         return resolveResource(errorViewName, model);
19     }

    --SpringBoot可以去找到某个页面,error/404,如果模板引擎可以解析这个地址,那么就是用模板引擎解析.如果模板引擎不可用,就在静态资源文件夹下寻找errorViewName对应的页面
    (2)一旦系统出现4XX或者5XX之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),就会来到/error请求,就会被BasicErrorController进行处理

 

二.如何定制错误响应
  1.如何定制错误的页面:在我们有模板引擎的情况下(error/404.html),将错误页面命名为错误状态码.html,放在模板引擎文件夹里面的error文件夹下,发生此状态码的错误就会来到对应的页面.如果我们为每一个状态码都配置一个错误页面的话,十分麻烦,我们可以给页面命名为4xx.html,这样当错误码不匹配的时候,将默认来到该html页面(优先寻找精确的状态码).
  当没有相对应的模板引擎时,将会在静态资源文件夹中寻找
  当模板引擎和静态资源的错误文件夹都没有的时候,将会来到SpringBoot的默认提示信息页面.
  2.如何定制错误的JSON数据

 1 package com.zhiyun.springboot.web_restfulcrud.exception;
 2 
 3 /**
 4  * @author : S K Y
 5  * @version :0.0.1
 6  */
 7 public class UserNotExistException extends RuntimeException {
 8     public UserNotExistException() {
 9         super("用户不存在");
10     }
11 }
1     @ResponseBody
2     @RequestMapping("/hello")
3     public String hello(@RequestParam("user") String user) {
4         if (user.equals("AAA")) {
5             throw new UserNotExistException();
6         }
7         return "hello";
8     }

 

三.自定义JSON错误数据响应
  1.使用SpringMvc的异常处理机制(ExceptionHandler)捕获到这个异常之后,运行自定义的方法

 1 package com.zhiyun.springboot.web_restfulcrud.controller;
 2 
 3 import com.zhiyun.springboot.web_restfulcrud.entity.Employee;
 4 import com.zhiyun.springboot.web_restfulcrud.exception.UserNotExistException;
 5 import org.springframework.web.bind.annotation.ControllerAdvice;
 6 import org.springframework.web.bind.annotation.ExceptionHandler;
 7 import org.springframework.web.bind.annotation.ResponseBody;
 8 
 9 import java.util.HashMap;
10 import java.util.Map;
11 
12 /**
13  * @author : S K Y
14  * @version :0.0.1
15  */
16 //在Spring中要成为一个异常处理器,需要使用该注解
17 @ControllerAdvice
18 public class MyExceptionHandler {
19 
20     @ResponseBody
21     @ExceptionHandler(UserNotExistException.class)
22     public Map handlerException(Exception e) {
23         Map map = new HashMap<>();
24         map.put("code", "user.not exist");
25         map.put("message", e.getMessage());
26         return map;
27     }
28 
29 }

  2.到目前为止还没有实现我们的自适应效果(如果是浏览器访问则返回页面,其他客户端访问则是返回JSON数据):

1     @ExceptionHandler(UserNotExistException.class)
2     public String handlerException(Exception e) {
3        /* Map map = new HashMap<>();
4         map.put("code", "user.not exist");
5         map.put("message", e.getMessage());*/
6         //转发到error请求
7         return "forward:/error";
8     }

  3.按照上述的方法,可以实现自适应的页面响应,但是此时响应的是默认的页面 ,想要实现自适应的效果,需要传入我们自定义的状态码:

1     @ExceptionHandler(UserNotExistException.class)
2     public String handlerException(Exception e, HttpServletRequest request) {
3         //传入我们自己的错误状态码
4         request.setAttribute("javax.servlet.error.status_code",500);
5         //转发到error请求
6         return "forward:/error";
7     }

  4.将我们的定制数据携带出去:
  (1)出现错误以后会来到error请求,这个请求会被BasicErrorController处理,他要返回的内容是由getErrorAttributes()方法得到的,而该方式是AbstractErrorController中随规定的方法,因此我们完全可以自定义一个Controller的实现类,或者是编写AbstractErrorController的子类来完成.
  (2)页面上能用的数据或者是JSON返回能用的数据都是通过errorAttributes.getErrorAttributes(requestAttributes,includeStackTrace);来得到的
  容器中DefaultErrorAttributes默认来进行数据处理,我们可以扩展该类来自定义返回信息:

 1 package com.zhiyun.springboot.web_restfulcrud.component;
 2 
 3 import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
 4 import org.springframework.web.context.request.RequestAttributes;
 5 
 6 import java.util.Map;
 7 
 8 /**
 9  * @author : S K Y
10  * @version :0.0.1
11  */
12 public class MyErrorAttributes extends DefaultErrorAttributes {
13     @Override
14     public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
15         Map map = super.getErrorAttributes(requestAttributes, includeStackTrace);
16         map.put("company", "skykuqi");
17         return map;
18     }
19 }

  (3)想要传递更多的信息,我们可以使用request来进行传递

 1     @ExceptionHandler(UserNotExistException.class)
 2     public String handlerException(Exception e, HttpServletRequest request) {
 3         //传入我们自己的错误状态码
 4         request.setAttribute("javax.servlet.error.status_code", 500);
 5         //传递错误信息
 6         Map map = new HashMap<>();
 7         map.put("code", "user.not exist");
 8         map.put("message", e.getMessage());
 9         request.setAttribute("errorMessage", map);
10         //转发到error请求
11         return "forward:/error";
12     }
 1 package com.zhiyun.springboot.web_restfulcrud.component;
 2 
 3 import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
 4 import org.springframework.web.context.request.RequestAttributes;
 5 
 6 import java.util.Map;
 7 
 8 /**
 9  * @author : S K Y
10  * @version :0.0.1
11  */
12 public class MyErrorAttributes extends DefaultErrorAttributes {
13     @Override
14     public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
15         Map map = super.getErrorAttributes(requestAttributes, includeStackTrace);
16         map.put("company", "skykuqi");
17         //自定义异常处理器所携带的数据
18         Map errorMap = (Map) requestAttributes.getAttribute("errorMessage", RequestAttributes.SCOPE_REQUEST);
19         map.put("errorMessage", errorMap);
20         return map;
21     }
22 }

  --最终的效果:我们可以定制ErrorAttributes来进行自定义错误信息的响应