SpringBoot——定制错误页面及原理

更多内容,前往 IT-BLOG

一、SpringBoot 默认的错误处理机制


【1】浏览器返回的默认错误页面如下:
 
☞ 浏览器发送请求的请求头信息如下:text/html 会在后面的源码分析中说到。
   
【2】如果是其他客户端,默认则响应错误的 JSON字符串,如下所示:
 
☞ 其他客户端发送请求的请求头信息如下: " */* " 源码中解释。
  

二、原理分析


参照 ErrorMvcAutoConfiguration类:错误处理的自动配置类,以下4项为此类的重要信息。

【1】ErrorMvcAutoConfiguration.ErrorPageCustomizer:当系统出现 4xx或者 5xx之类的错误时,ErrorPageCustomizer就会生效(定制错误的响应规则),根据如下源码可知,将会来到 /error请求。

 1 @Bean
 2 public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
 3     return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties);
 4 }
 5 
 6 //进入ErrorPageCustomizer方法,发现registerErrorPages方法:注册一个错误也
 7 private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
 8     private final ServerProperties properties;
 9 
10     protected ErrorPageCustomizer(ServerProperties properties) {
11         this.properties = properties;
12     }
13 
14     public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
15         ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + 
16                                 this.properties.getError().getPath());
17         errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
18     }
19 }
20 
21 //进入this.properties.getError().getPath()方法,获取如下信息,得到/error请求。
22 @Value("${error.path:/error}")
23 private String path = "/error";//系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)

【2】BasicErrorController 处理 /error错误请求:注意:text/html 和 */*就是在此处生效。

 1 @Bean
 2 @ConditionalOnMissingBean(
 3     value = {ErrorController.class},
 4     search = SearchStrategy.CURRENT
 5 )
 6 public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
 7     return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
 8 }
 9 
10 //进入BasicErrorController对象,获取如下信息
11 @Controller
12 @RequestMapping({"${server.error.path:${error.path:/error}}"})
13 public class BasicErrorController extends AbstractErrorController {
14     private final ErrorProperties errorProperties;
15 
16     public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
17         this(errorAttributes, errorProperties, Collections.emptyList());
18     }
19 
20     @RequestMapping(
21         produces = {"text/html"}//产生html类型的数据;浏览器发送的请求来到这个方法处理
22     )
23     public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
24         HttpStatus status = this.getStatus(request);
25         Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
26         response.setStatus(status.value());
27 
28         //去哪个页面作为错误页面;包含页面地址和页面内容
29         ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
30         return modelAndView != null?modelAndView:new ModelAndView("error", model);
31     }
32 
33     @RequestMapping
34     @ResponseBody//产生json数据,其他客户端来到这个方法处理;
35     public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
36         Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
37         HttpStatus status = this.getStatus(request);
38         return new ResponseEntity(body, status);
39     }
40 }

☞ 如上代码中提到的错误页面解析代码,进入此方法: this.resolveErrorView(request, response, status, model);

 1 protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
 2     Iterator var5 = this.errorViewResolvers.iterator();
 3     ModelAndView modelAndView;
 4     do {
 5         //从所有的ErrorViewResolver得到ModelAndView
 6         if(!var5.hasNext()) {
 7             return null;
 8         }
 9 
10         ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
11         modelAndView = resolver.resolveErrorView(request, status, model);
12     } while(modelAndView == null);
13 
14     return modelAndView;
15 }

【3】最终的响应页面是由 DefaultErrorViewResolver 解析得到的:最重要的信息是,SpringBoot 默认模板引擎的 /error目录下获取 ‘status’.xml 错误页面,也可以通过 4xx.xml来统配 404.xml和 400.xml等等,但是优先获取精准的页面。如果模板引擎中不存在,则会从静态页面中获取错误页面。否则返回系统默认错误页面。

 1  @Bean
 2  @ConditionalOnBean({DispatcherServlet.class})
 3  @ConditionalOnMissingBean
 4  public DefaultErrorViewResolver conventionErrorViewResolver() {
 5         return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
 6     }
 7 
 8 //进入DefaultErrorViewResolver类中
 9 public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
10     ModelAndView modelAndView = this.resolve(String.valueOf(status), model);
11     if(modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
12 
13         //调用时viewname = status ***重要
14         modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
15     }
16 
17     return modelAndView;
18 }
19 
20 private ModelAndView resolve(String viewName, Map<String, Object> model) {
21 
22     //默认SpringBoot可以去找到一个页面? error/404
23     String errorViewName = "error/" + viewName;
24 
25     //模板引擎可以解析这个页面地址就用模板引擎解析
26     TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.
27     getProvider(errorViewName, this.applicationContext);
28 
29     //模板引擎可用的情况下返回到errorViewName指定的视图地址,
30     //当模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
31     return provider != null?new ModelAndView(errorViewName, model):this.resolveResource(errorViewName, model);
32 }

【4】DefaultErrorAttributes:在页面添加错误信息,供我们使用。

 1 @Bean
 2 @ConditionalOnMissingBean(
 3     value = {ErrorAttributes.class},
 4     search = SearchStrategy.CURRENT
 5 )
 6 public DefaultErrorAttributes errorAttributes() {
 7     return new DefaultErrorAttributes();
 8 }
 9 
10 //进入DefaultErrorAttributes类中,发现此方法给视图中添加了status状态等信息,供我们使用。
11 public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
12     Map<String, Object> errorAttributes = new LinkedHashMap();
13     errorAttributes.put("timestamp", new Date());
14     this.addStatus(errorAttributes, requestAttributes);
15     this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
16     this.addPath(errorAttributes, requestAttributes);
17     return errorAttributes;
18 }

三、定制错误 JSON数据


【1】自定义异常处理类,返回定制的 JSON数据。通过上述的分析,我们得知:①、可以完全编写一个 ErrorController的实现类,或者继承 AbstractErrorController的子类,放入容器中。②、也可以自定义异常处理类,返回 JSON数据。③、页面上的数据或 JSON返回的数据都是可以通过 errorAttributes.getErrorAttributes得到的。我们可以自定义属于自己的 ErrorAttributes。

 1 //首先我们可以通过自定义异常处理,来确定返回的数据,但这个不够灵活,我们可以与③结合使用
 2 /**
 3  * @RequestMapping启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping
 4  * 注解的方法上。
 5  */
 6 @ControllerAdvice
 7 public class MyExceptionHandler {
 8     @ResponseBody
 9     @ExceptionHandler(UserNotExistException.class)
10     public Map<String,Object> handlerException(Exception e, HttpServletRequest request){
11         Map<String,Object> map = new HashMap<String,Object>();
12         request.setAttribute("javax.servlet.error.status_code","500");
13         map.put("code","user.notexist");
14         map.put("message",e.getMessage());
15         return map;
16     }
17 }
18 
19 //③自定义ErrorAttributes,一定要加入容器
20 @Component
21 public class MyErrorAttributes extends DefaultErrorAttributes{
22     @Override
23     public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
24         //获取默认的配置,在此基础上添加自己的需求
25         Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
26         //自定义自己需要的属性
27         map.put("company","yintong");
28 
29         //获取我们在异常处理类中添加的信息,
30         /*注意:当我们需要结合使用的时候异常处理必须return "forward:/error";将请求转发出去,不能直接返回map对象,
31         同时要去掉@responseBody注解,否则ErrorAttributes不生效*/
32         map.put("ext",requestAttributes.getAttribute("ext",requestAttributes.SCOPE_REQUEST));
33         return map;
34     }
35 }

【2】效果展示:
  


 ----关注公众号,获取更多内容----

posted @ 2020-11-22 10:19  Java程序员进阶  阅读(18)  评论(0编辑  收藏  举报