ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot 自动装配原理浅探

2022-05-19 12:33:55  阅读:159  来源: 互联网

标签:装配 SpringBoot spring 配置 浅探 step 监听器 注解 上下文


1 pom.xml

1.1 项目依赖管理

打开项目的配置文件 pom.xml,看起来和以前的 ssm 项目差不多,仔细观察发现,以前的依赖都是由 GAV 三部分构成

但是在 SpringBoot 中没有了 V(版本号),这是为什么?

image-20220518101343338

难道依赖没有版本号吗?显然不可能。继续往下找,发现有一个 <dependencyManagement> 的配置,看名字似乎是依赖管理器?

image-20220518101857803

点击进入 spring-boot-dependencies ,我们发现,原来 SpringBoot 将版本号都在 spring-boot-dependencies-xxx.pom 文件里统一进行管理了

image-20220518101940275

1.2 启动器

配置文件中,我们稍微仔细观察,会发现一个规律:好像所有的依赖都是以 spring-boot-starter 开头的?

image-20220518103639520

这应该不是偶然,我们去 SpringBoot [官方文档](Developing with Spring Boot) ,官方这里有对于启动器的描述:

image-20220518104133407

启动器是一个方便于我们在项目中引入依赖的集合

官方启动器的名字都是类似于 spring-boot-starter-* 的形式,这可以很方便的帮助我们快速引入项目所需要的环境以来

比如想要引入关于 aop 的依赖,只需要加上 aop 启动器,即 spring-boot-starter-aop 即可,需要什么功能,只需要加入对应的启动器

2 主启动类的注解

项目的主启动类只有寥寥几行代码,那为什么就能启动整个项目?

package com.jiuxiao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBoot01Application {
    
    public static void main(String[] args) {
        SpringApplication.run(SpringBoot01Application.class, args);
    }
}

我们首先通过研究注解来探究 SpringBoot 自动装配的原理

2.1 @SpringBootApplication

这个注解,顾名思义,表明它是一个 SpringBoot 应用,我们点进去该注解,发现有很多的注解,除过那几个无关紧要的元注解之外,值得我们注意的注解有 @SpringBootConfiguration@EnableAutoConfiguration 这两个

image-20220518110927201

2.2 @SpringBootConfiguration

该注解表明它是一个 SpringBoot 的配置,依次点进去该注解

image-20220518111606595

很清楚的可以看到,主启动类上的 @SpringBootConfiguration 注解,本质上是一个 Spring 的组件

2.3 @EnableAutoConfiguration

点进该注解,除过元注解外,有两个重要的注解

image-20220518142325950

  • 对于第一个注解,@AutoConfigurationPackage ,意思就是自动配置包,那么,它配置了什么东西?

再点进去该注解,发现它是导入了一个配置包选择器选择器,导入了什么选择器?

image-20220518142653705

再往里进入,发现 AutoConfigurationPackages.Registrar 注册了一些 bean,然后导入了一些元数据,这些元数据估计与包扫描有关,这里先不深入

image-20220518142950661

  • 对于第二个注解,@Import(AutoConfigurationImportSelector.class),它导入了一些选择器

进入 AutoConfigurationImportSelector 选择器 ,里面有一个名为 getAutoConfigurationEntry 的方法,根据名称可以知道,该方法是自动获取配置项目

方法中有一句如下所示代码,它的作用是获取候选配置列表

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

image-20220518143558001

那么怎么获取候选配置列表?点进去 核心方法 getCandidateConfigurations() 方法

首先,看 getCandidateConfigurations() 方法的第一行代码

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
    getSpringFactoriesLoaderFactoryClass(),
	getBeanClassLoader()
);

一共传递了两个参数,第二个参数 getBeanClassLoader() 应该就是使用 bean 加载器加载进来了一些 bean,很好理解

第一个参数 getSpringFactoriesLoaderFactoryClass() 是一个方法,我们去看该方法,该方法只有一个返回值

就是返回了 EnableAutoConfiguration 的 class 文件,这个 EnableAutoConfiguration 有点似曾相识?不正是我们一直在研究的这个注解 @EnableAutoConfiguration 吗?

兜兜转转一圈,我们明白了,@EnableAutoConfiguration 注解作用之一就是为了导入启动类之下的所有资源!

然后再看 getCandidateConfigurations() 方法的第二行代码

Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you ...");

它断言了一个配置文件 META-INF/spring.factories 非空,换个角度想想,是不是只要该配置文件非空,它就会被加载?那么我们就去找到该配置文件

我们在项目所有依赖的 jar 包中,找到一个名为 spring-boot-autoconfigure-xxxxx.jar 的包

在它这个 jar 包里面,我们找到了断言处所提到的配置文件 spring.factories,那么不出意外的话,它应该就是自动配置的核心文件了

image-20220518150242015

打开该文件,我们看看他到底都配置了什么东西?可以看到,配合了很多很多的配置,那么,为什么读取了这个文件后,他就能自动装配好?

image-20220518150607263

我们以我们熟悉的 WebMvc 的配置为例来进行分析,点进去 WebMvcAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,

我们发现它是一个配置类,在里面我们找到了 ssm 框架中我们所熟悉的一些东西,比如静态资源过滤、视图解析器等等

//静态资源过滤
public void addResourceHandlers() {}
//资源配置链
private void configureResourceChain() {}
//视图解析器
private ResourceResolver getVersionResourceResolver() {}

至此,我们大致明白了,getCandidateConfigurations() 方法,它先通过 EnableAutoConfiguration 的 class 文件,利用反射机制来获取到当前启动类下的所有资源文件,然后再去读取核心配置文件 META-INF/spring.factories,利用该配置文件中的配置,去找到配置文件中所设计的所有配置类

我们再去看 getCandidateConfigurations() 方法的第一行代码,方法 loadFactoryNames() 里面的两个参数我们刚刚在上面已经分析过,现在看方法本身

点进去 loadFactoryNames() 方法,该方法就做了一件事,调用 loadSpringFactories() 方法,依然去读取 META-INF/spring.factories 这个核心配置文件,然后将获取到的所有资源配置类全部放在一个名为 properties 的配置类中,所以该配置类 properties 就可以直接供我们使用!

image-20220518153242641

以上重要注解的原理图大致如下:

@ComponentScan、@SpringBootConfiguration

image-20220518172730932

@@EnableAutoConfiguration(核心注解)

image-20220518173105033

思考:经过上面的分析,我们已经知道,SpringBoot 会将从 META-INF/spring.factories 中读取并加载的所有配置类全部添加到一个名为 properties 的配置类中,供我们直接使用。那么,既然所有的配置类都被加载了,为什么很多都没有生效,需要我们去在 pom.xml 中导入对应的 starter 才会生效?

我们去 spring.factories 文件中,随意找一个我们没有使用的配置类,比如下面的 security.reactive.ReactiveSecurityAutoConfiguration 配置类

image-20220518170259033

可以很清楚的看到,编译器中直接报红,这意味着我们这些包并没有进行导入

再看该类上面的 @ConditionalOnClass 注解,该注解的作用就是判断该配置类是否被用户在项目中使用 spring-boot-strater-xxx 的形式引入

如果没有使用 starter 的形式进行引入,则虽然被加载,但不会生效,这也就是为什么全部配置类都被导入了,但只有使用 starter 后才会生效的原因

2.4 注解小结
  • SpringBoot 在启动的时候,会直接从 /META-INF/spring.factories 文件中来获取指定的配置类

  • 获取到这些配置类的全限定名之后,就会将这些自动配置类导入 Spring 容器中,接下来 Spring 就会帮助我们进行自动配置

  • 在 SpringBoot 项目中,自动装配的方案和配置,都在 spring-boot-autoconfigure-xxxx.jar 这个 jar 包中

  • 容器中会存在非常多的 xxxAutoConfiguration 文件(本质仍然是一个个 bean),就是这些自动配置类,给 Spring 容器中导入了各种场景下所需要的组件,并进行了自动装配

  • 有了这些自动配置类,就免去了我们自己去编写配置文件的流程

3 主启动类

SpringApplication 类主要做了以下几件事情:

  • 推断应用的类型是普通 Java 项目还是 Web 项目

  • 查找并初始化所有的可用初始化器,设置到 initlizers 属性中

  • 找出所有应用程序的监听器,设置到 listeners 属性中

  • 推断并设置 main 方法的定义类,找到运行的主类(通过传入的当前类的 class 文件来推断)

该类的构造器初始化了以下属性,比如资源、控制台日志、注册关机、自定义环境、资源部加载器、初始化器、监听器、主程序类等等

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

4 run 方法流程探究

为了研究 run 方法到底干了什么事,才可以让整个项目启动,我们给 run 方法打上断点一步步进行调试

run 方法执行一共分为三个大阶段:准备启动阶段、正式启动阶段、启动结束阶段

  1. new SpringApplication()、init 加载初始化

当启动 SpringApplication 之后,首先创建了一个 SpringApplication 的实例

然后去执行 SpringApplication 的构造函数,使用构造函数进行 init() 初始化,一共做了以下四步操作:

  • 根据类路径推断应用是否为 Web 项目

  • 加载所有可用的初始化器

  • 设置所有可用的程序监听器

  • 推断并设置 main 方法的定义类

image-20220519100151841

  1. 开始执行 run() 方法

然后 run() 方法开始准备执行,他首先会实例化一个监听器,这个监听器在启动之后会持续监听应用程序上下文

image-20220519101343404

  1. step1 : headless 系统属性设置

该阶段中,程序开始设置 headless 相关的系统属性(下方流程图的 step 1)

image-20220519112414963

  1. step 2 : 初始化监听器 getRunListener(args)

程序将前面实例化的监听器进行初始化设置(下方流程图的 step 2)

image-20220519112529644

然后会使用 SpringFactoriesInstances 来根据传入的对象名来得到所需的工厂对象

这里的对象名,就是从 2.2 中提到的 spring-boot-autoconfigure-xxxx.jar 这个 jar 包下的 /META-INF/spring.factories 文件中所获取的

这个文件中配置了所有的自动配置对象的全限定名,工厂对象会根据该对象的 class 文件,使用反射机制得到该对象的构造犯法,最后生成一个工厂的实例对象并返回

image-20220519113436666

  1. step 3 : 启动准备好的监听器

然后将初始化完成的监听器正式启动

这个监听器会持续监听上下文,直到上下文发布完成并返回之后,它才会停止监听(下方流程图的 step 3)

image-20220519112736206

  1. step 4 : DefaultApplicationArguments

开始装配环境参数,创建了 web/standard 环境、 加载了属性源、加载了预监听集合

到此步骤为止,应用的 准备启动阶段 已经完成!(下方流程图的 step 4)

image-20220519102109234

image-20220519102514132

image-20220519102932740

  1. step 5 : 打印 banner 图案

这一步开始,应用正式开始启动,首先会打印 banner 图案(下方流程图的 step 5)

image-20220519103410075

image-20220519103522682

  1. step 6/6.1 : 上下文区域、根据类型创建上下文

到了这里就开始创建上下文区域

程序会根据 web/standard 的类型来创建与之对应的上下文(下方流程图的 step 6、step 6.1)

image-20220519103721655

  1. step 7 : 准备上下文异常报告

这一步骤中,程序会根据 SpringFactoriesInstances 来创建对应的上下文异常报告(下方流程图的 step 7)

image-20220519104055593

  1. step 8 : 上下文前置处理 prepareContext

该步骤会对上下文进行前置处理,包括监听器的配置、相关环境配置、初始化器设置、资源加载等操作

至此,上下文的前置准备工作结束(下方流程图的 step 8)

image-20220519104550201

  1. step 9 : 上下文刷新 refreshContext

step 8 中上下文初始化完成之后,接下来就是给上下文中写入东西(刷新上下文)

在该步骤中,程序会加载 bean 工厂、生产所有的 bean、完成 bean 工厂的初始化,最后再次刷新生命周期(下方流程图的 step 9)

image-20220519105931555

关于上下文的所有操作结束以后,程序启动阶段的所有环境均已经基本就绪

此时 Tomcat 相关的服务就会开始启动了

image-20220519110648027

  1. step 10/11 : 上下文结束后处理 afterRefresh、发布上下文

这一步骤,就是应用启动阶段的最后一步,到这一步骤的时候,上下文已经被刷新、所有的 Bean 也已经被 bean 工厂生产完毕并写入进上下文,上下文相关的操作已经到尾声,接下来就是收尾工作,即上下文后处理、停止计时器、停止监听器的相关操作,处理完这些工作后就会正式发布上下文(下方流程图的 step 10、step 11)

image-20220519110822801

  1. step 12/13 : 执行 Runner 运行器、上下文就绪并返回

接下来程序会调用 Runner() 运行器,并且发布应用上下文就绪的信号,然后返回

至此,正式启动阶段 结束(下方流程图的 step 12、step 13)

image-20220519111300804

image-20220519111402975

至此,SpringApplication 启动完成!(启动结束阶段

run() 方法执行的大致的流程图如下所示:

标签:装配,SpringBoot,spring,配置,浅探,step,监听器,注解,上下文
来源: https://www.cnblogs.com/wudaojiuxiao/p/16288071.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有