SpringBoot源码学习(二) 初始化环境,创建容器,初始化Failure Analyzers

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: ## 前言 + 第一篇文章我们大概了解了springboot启动的时候主要做了这么几件事 + new了一个SpringApplication实例 + 判断当前spring运行的环境 + 加载META-INF/spring.factories 并初始化监听器 + SpringApplications实例.run + 获取并启动监听器 + 实例化Even

前言

  • 第一篇文章我们大概了解了springboot启动的时候主要做了这么几件事
  • new了一个SpringApplication实例

    • 判断当前spring运行的环境
    • 加载META-INF/spring.factories 并初始化监听器
  • SpringApplications实例.run

    • 获取并启动监听器
    • 实例化EventPublishingRunListener
    • 把所有通过spring.factories实例化的listener添加到SimpleApplicationEventMulticaster中。
    • 发布ApplicationStartingEvent 执行注册了这个事件的listener
    • 构造容器环境
    • 创建容器
    • 实例化Failure Analyzers 处理启动的报错信息
    • 准备容器
    • 刷新容器
    • 刷新容器的后扩展接口
  • 今天会分析

    • 构造容器环境
    • 创建容器
    • 实例化Failure Analyzers 处理启动的报错信息

构造容器环境

  • 瞅一眼代码
    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }
#### getOrCreateEnvironment
+ 先看getOrCreateEnvironment()其实这个方法就是根据容器运行的环境  动态new个Environment类, 对于正常的WEB请求new的就是StandardServletEnvironment()这个类
+ 我们看一下StandardServletEnvironment的继承树
![image.png](https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/2cccd57c0ad5d3831a70e5688813d6ed.png)
+ 我们debug进去发现new StandardServletEnvironment()的时候,其实就是执行的起点在AbstractEnvironment这个类上, 会执行customizePropertySources()方法
![image.png](https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/8e94bf142d9aad4285c52e7aefb38ec8.png)
+ 我们因为new的是StandardServletEnvironment这个类 所以会执行这个类的customizePropertySources的方法, 这个方法一里面先把servletContextInitParams, servletConfigInitParams的定义放到propertySources里面, 再执行父类的customizePropertySources方法 
![image.png](https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/2cfc989f77a8106075648f81ff16ac8c.png)
+ 这是一个关键点 在执行父类方法的时候, 实际上父类已经把系统变量,和系统环境变量给放到propertySources里面了
![image.png](https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/becfc08ff79a9d6105d33ae9e2d446ab.png)

#### configureEnvironment
+ configurePropertySources 会把通过命令行传入的参数放到propertySources 里面 我们默认为空 所以为空
+ configureProfiles 会设置active属性
    + 如果在执行时显示指定了环境 会先用指定的环境
     
    SpringApplication springApplication = new SpringApplication(MyApplication.class);
        //设置profile变量
        springApplication.setAdditionalProfiles("prd");
        springApplication.run(MyApplication.class,args);
     
+ 如果指定了spring.profile.active 会在这里被加载到 否则为空

listeners.environmentPrepared(environment)

  • 这里会把ApplicationEnvironmentPreparedEvent时间发送给注册这个事件的listener
  • 比较重要的是ConfigFileApplicationListener

image.png

  • 里面有一个内部类Loader会对代码注册变量文件进行解析, 比如我们新增了classpath:application.properties classpath:application-default.properties
  • 最终我们可以看到我们的Environment中加载了这些数据

image.png

  • 至此,springBoot中的资源文件加载完毕,解析顺序从上到下,所以前面的配置文件会覆盖后面的配置文件。可以看到application.properties的优先级最低,系统变量和环境变量的优先级相对较高。

创建容器

  • 惯例瞅一眼源码
context = createApplicationContext();
  • 跟进进去会发现代码异常简单
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
  • 其实就是做了两件事

    • 根据当前的容器环境获取应该进行实例化的类, SERVLET类型会得到org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
    • 实例化这个类
    • 7.27注: 这里有坑,没有想象的那么简单 此处对reader、scanner、beanFactory进行了实例化;reader中实例化了属性conditionEvaluator;scanner中添加了两个AnnotationTypeFilter:一个针对@Component,一个针对@ManagedBean;beanFactory中注册了8个注解配置处理器 但是我太懒了 还没有对这里进行补充。
    • 7.27注2: 吃了一个雪糕之后决定还是补充完, 因为多吃了一个雪糕晚上要加2公里了 吃雪糕真是一个糟糕的决定啊。
    • 我们debug进AnnotationConfigServletWebServerApplicationContext这个类发现对Reader和Scanner做了初始化

image.png

+ AnnotatedBeanDefinitionReader 
      + 往ApplicationContext里面塞了一个conditionEvaluator  这个Evaluator是用来处理 @conditional注解的
       + 注册了多个Processors 执行后我们发现 我们的ApplicationContext里的BeanFactory里面多了6个Processors 

image.png

+ ClassPathBeanDefinitionScanner 一看名字就知道是一个bean定义扫描器,用来扫描类路径下的bean侯选者。
     + debug进去 核心代码是注册了两个核心的AnnotationTypeFilter 一个针对@Component,一个针对@ManagedBean

image.png

实例化Failure Analyzers 处理启动的报错信息

  • 扫一下源码
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
  • 我们debug进去看一下
    private  Collection getSpringFactoriesInstances(Class type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
  • 其实如果前面几步都进行过单步调试的话 对这个方法一定不会陌生, 他的核心就是根据入参type的定义 去spring.factories里面找到对应的实现类通过反射进行实例化。

image.png

  • 我们去看一下org.springframework.boot.diagnostics.FailureAnalyzers的构造方法
    FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
        Assert.notNull(context, "Context must not be null");
        this.classLoader = (classLoader != null) ? classLoader : context.getClassLoader();
        this.analyzers = loadFailureAnalyzers(this.classLoader);
        prepareFailureAnalyzers(this.analyzers, context);
    }
  • 这个构造方法的核心就是loadFailureAnalyzers
    private List loadFailureAnalyzers(ClassLoader classLoader) {
        List analyzerNames = SpringFactoriesLoader
                .loadFactoryNames(FailureAnalyzer.class, classLoader);
        List analyzers = new ArrayList<>();
        for (String analyzerName : analyzerNames) {
            try {
                Constructor<?> constructor = ClassUtils.forName(analyzerName, classLoader)
                        .getDeclaredConstructor();
                ReflectionUtils.makeAccessible(constructor);
                analyzers.add((FailureAnalyzer) constructor.newInstance());
            }
            catch (Throwable ex) {
                logger.trace("Failed to load " + analyzerName, ex);
            }
        }
        AnnotationAwareOrderComparator.sort(analyzers);
        return analyzers;
    }
  • 其实也简单, 就是把spring.factories里面的FailureAnalyzer.class对应的类全部加载回来并实例化

image.png

  • 那么这些Analyzers是怎么被使用的呢? 我们发现启动流程本身会有一个大的try-catch

image.png

  • 最终handleRunFailure 这个方法会执行到reportFailure 这个方法里

image.png

  • 核心是这个for循环 他会遍历 实例化的Analyzers 如果Analyzer发现 当前的ex自己处理不了 就会返回null 这样就会流转到下一个Analyzers 通过这种方案 就可以通过这些Analyzers 对错误进行更详细更专业的透出。
相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
目录
相关文章
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
3月前
|
前端开发 Java
表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
文章通过一个表白墙/留言墙的初级SpringBoot项目实例,详细讲解了如何进行前后端开发,包括定义前后端交互接口、创建SpringBoot项目、编写前端页面、后端代码逻辑及实体类封装的全过程。
108 3
表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
3月前
|
前端开发 Java 数据安全/隐私保护
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
文章通过一个简单的SpringBoot项目,详细介绍了前后端如何实现用户登录功能,包括前端登录页面的创建、后端登录逻辑的处理、使用session验证用户身份以及获取已登录用户信息的方法。
493 2
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
|
12天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
22天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
107 13
|
30天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
负载均衡 网络协议 算法
Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式
本文探讨了Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式,以及软件负载均衡器、云服务负载均衡、容器编排工具等实现手段,强调两者结合的重要性及面临挑战的应对措施。
78 3
|
2月前
|
Kubernetes 容器 Perl
【赵渝强老师】K8s中Pod中的初始化容器
Kubernetes的Pod包含业务容器、基础容器、初始化容器和临时容器。初始化容器在业务容器前运行,用于执行必要的初始化任务。本文介绍了初始化容器的作用、配置方法及优势,并提供了一个示例。
|
3月前
|
缓存 Java Spring
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
文章比较了在Servlet和Spring Boot中获取Cookie、Session和Header的方法,并提供了相应的代码实例,展示了两种方式在实际应用中的异同。
225 3
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
|
2月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。