• 峰哥说技术:14-Spring Boot异常处理方案源码解析与实践


    Spring Boot深度课程系列

    峰哥说技术—2020庚子年重磅推出、战胜病毒、我们在行动

    14  峰哥说技术:Spring Boot异常处理方案源码解析与实践

    虽然我们可以@ControllerAdvice注解配置@ExceptionHandler来处理全局异常。但是也可以有自己的方案,在Spring Boot中对异常的处理有一些默认的策略,我们可以通过一个案例来看。

    案例:构造一个工程,访问http://localhost:8080/hello.在接口方法如下。

    步骤:

    1)创建包,编写HelloController.代码如下

    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello(){
            int i=1/0;
            return "hello,Spring boot!";
        }
    }

    2)在浏览器中输入:http://localhost:8080/hello.结果如下:

     

    错误分析,明显的可以看到当除数为零的时候出现了异常,自动映射到/error路径,而在程序中没有/error路径。所以到了整个页面。不过在 Spring Boot 中,提供 /error 路径实际上是下下策,Spring Boot 本身在处理异常时,也是当所有条件都不满足时,才会去找 /error 路径。那么我们就先来看看,在 Spring Boot 中,如何自定义 error 页面,整体上来说,可以分为两种,一种是静态页面,另一种是动态页面。那么该怎么使用?相信经过这么多次课程的学习,我们想到的一定是找源码。

    其实在Spring Boot中,有一个类ErrorMvcAutoConfiguration的配置类,在该类中我们首先看源码中的静态类DefaultErrorViewResolverConfiguration

    @Configuration(
        proxyBeanMethods = false
    )
    static class DefaultErrorViewResolverConfiguration {
        private final ApplicationContext applicationContext;
        private final ResourceProperties resourceProperties;

        DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
            this.applicationContext = applicationContext;
            this.resourceProperties = resourceProperties;
        }
        @Bean
        @ConditionalOnBean({DispatcherServlet.class})
        @ConditionalOnMissingBean({ErrorViewResolver.class})
        DefaultErrorViewResolver conventionErrorViewResolver() {
            return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
        }
    }

    红色字体部分的DefaultErrorViewResolver对象很显然定义的默认的错误视图解析器,那么错误的视图肯定跟这个解析器有关。下面研究这个类。在该类中有一个方法。

    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }

        return modelAndView;
    }

    很显然是来解析错误视图的方法,如果去跟踪status.series()方法,在这里,首先以异常响应码作为视图名分别去查找动态页面和静态页面,如果没有查找到,则再以 4xx 或者 5xx 作为视图名再去分别查找动态或者静态页面。然后观察第2处红色字体部分。我们发现有一段静态代码块,代码如下:

    static {
        Map<Series, String> views = new EnumMap(Series.class);
        views.put(Series.CLIENT_ERROR, "4xx");
        views.put(Series.SERVER_ERROR, "5xx");
        SERIES_VIEWS = Collections.unmodifiableMap(views);
    }

    很显然,动态错误视图的名称是4xx.html或者5xx.html。然后再跟进第2处红色字体方法。代码如下:

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        String errorViewName = "error/" + viewName;
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

    1处红色字体部分,很明显说明视图有前缀error,而且是固定格式。第2处红色部分是解析资源的代码。我们继续跟进去。

    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
        String[] var3 = this.resourceProperties.getStaticLocations();
        int var4 = var3.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];

            try {
                Resource resource = this.applicationContext.getResource(location);
                resource = resource.createRelative(viewName + ".html");
                if (resource.exists()) {
                    return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
                }
            } catch (Exception var8) {
                ;
            }
        }
        return null;
    }

    我们继续追踪第1处红色字体,很容易就进入了ResourceProperties类中。可以看到它实际就是前面的静态资源的位置。

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

    那我们答案就呼之欲出了,我们定义的错误页面就是放上面静态资源的的路径下面的error文件夹下面。而且它的扩展名必须是.html.

    A)自定义静态异常页面

    步骤:

    1)static下面创建error文件夹,添加500.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h4>500错误页</h4>
    </body>
    </html>

    2)templates下面创建error文件夹,添加500.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h4>templates-500错误页</h4>
    </body>
    </html>

    3)测试:在浏览器输入http://localhost:8080/hello

     

    很显然使用的是static/error文件夹下面的页面。删除后,会使用templeates/error文件夹下面的页面。大家可以自行测试即可。

    B)自定义动态异常页面

    步骤:

    1)static下面创建error文件夹,添加5xx.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h4>5xx错误页</h4>
    </body>
    </html>

    2)在templates下面创建error文件夹,添加5xx.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h4>templates-5xx错误页</h4>
    </body>
    </html>

    3)测试:将static/error文件夹下的500.html改成500-bak.html,在浏览器输入http://localhost:8080/hello

     

    最后得出结论:

    发生了500错误-->查找动态 500.html 页面-->查找静态 500.html --> 查找动态 5xx.html-->查找静态 5xx.html。

  • 相关阅读:
    JAVA SSH 框架介绍
    Web开发者不可不知的15条编码原则
    全选,反选,全不选
    Python函数
    Python变量解析
    Python输入/输出语句
    Python程序基本架构
    Python开发环境安装
    java事件
    测试博客
  • 原文地址:https://www.cnblogs.com/027kgc/p/12502153.html
Copyright © 2020-2023  润新知