SpringBoot原理深入剖析
依赖管理
构建spring boot工程时,统一引入的依赖spring-boot-starter-parent
1 | <parent> |
代码底层引入了spring-boot-dependencies ,核心代码如下
1 | <parent> |
继续深究底层
1 | <properties> |
从这可以看出, 该文件通过变迁对一些常用技术框架的依赖文件进行了统一管理,以至于我们在开发过程中,不用自己引入对应的依赖
自动配置
Spring Boot应用程序入口是@SpringBootApplication 注解标注类的main()方法, @SpringBootApplication 能够扫描Spring组件并自动配置, 下面看@SpringBootApplication 内部源码剖析
1 |
|
1 | //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中 (ElementType.TYPE) |
从上述源码可以看出,@SpringBootApplication 注解是一个组合注解,主要功能由@SpringBootConfiguration̵ @EnableAutoConfiguration̵ @ComponentScan 三个核心注解组成
@SpringBootConfiguration 注解表示Spring Boot配置类, 实际是对 @Configuration 注解一个名称的改写,底层用的还是Spring提供的功能
1 | ({ElementType.TYPE}) |
@EnableAutoConfiguration 注解表示开启自动配置功能,该注解也是最重要的注解, 也是实现了自动化配置的注解, 核心代码如下:
1 | (ElementType.TYPE) |
可以发现,它也是一个组合注解,Spring中有很多以Enable开头的注解,其作用就算借助@Import来收集并注册特定场景相关的bean,并加载导IoC容器中.
其核心两个注解分别是
@AutoConfigurationPackage
1
2
3
4
5
6
7
8
9
10
11(ElementType.TYPE)
(RetentionPolicy.RUNTIME)
//spring框架的底层注解,它的作用就是给容器中导入某个组件类,
//例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中
.class) // 默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中 (AutoConfigurationPackages.Registrar
public @interface AutoConfigurationPackage {
}其中@Import(AutoConfigurationPackages.Registrar.class) 就是将Registrar类,加载导容器中去,查看Registrar 类的registerBeanDefinitions ,就算导入过程的具体实现:
1
2
3
4
5
6
7
8
9
10
11// 获取的是项目主程序启动类所在的目录
//metadata:注解标注的元数据信息
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
register(registry, new PackageImport(metadata).getPackageName());
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}@Import({AutoConfigurationImportSelector.class})
将AutoConfigurationImportSelector 这个类导入导容器中, AutoConfigurationImportSelector 可以借助SpringBoot应用程序将所有符合调价的@Configuration 配置加载到容器中, 继续深究AutoConfigurationImportSelector源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
//作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
// 自动配置的类全名.条件=值
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}深究loadMetadata方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
//重载方法
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
//1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
// 获得 PATH 对应的 URL 们
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
// 遍历 URL 数组,读取到 properties 中
Properties properties = new Properties();
//2.解析urls枚举对象中的信息封装成properties对象并加载
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
// 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象
//根据封装好的properties对象生成AutoConfigurationMetadata对象返回
return loadMetadata(properties);
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}回到AutoConfigurationImportSelector类 深究getAutoConfigurationEntry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// 1. 判断是否开启注解。如未开启,返回空串
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 2. 获得注解的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
// 将这些值作为自动配置类导入到容器中,自动配置类就生效了
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
configurations = removeDuplicates(configurations);
// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
checkExcludedClasses(configurations, exclusions);
// 4.2 从 configurations 中,移除所有不希望自动配置的配置类
configurations.removeAll(exclusions);
// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类
//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
//@ConditionalOnMissingClass : classpath中不存在该类时起效
//@ConditionalOnBean : DI容器中存在该类型Bean时起效
//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
//@ConditionalOnExpression : SpEL表达式结果为true时
//@ConditionalOnProperty : 参数设置或者值一致时起效
//@ConditionalOnResource : 指定的文件存在时起效
//@ConditionalOnJndi : 指定的JNDI存在时起效
//@ConditionalOnJava : 指定的Java版本存在时起效
//@ConditionalOnWebApplication : Web应用环境下起效
//@ConditionalOnNotWebApplication : 非Web应用环境下起效
//总结一下判断是否要加载某个类的两种方式:
//根据spring-autoconfigure-metadata.properties进行判断。
//要判断@Conditional是否满足
// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
configurations = filter(configurations, autoConfigurationMetadata);
// 6. 将自动配置导入事件通知监听器
//当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
// 并触发fireAutoConfigurationImportEvents事件。
fireAutoConfigurationImportEvents(configurations, exclusions);
// 7. 创建 AutoConfigurationEntry 对象
return new AutoConfigurationEntry(configurations, exclusions);
}@EnableAutoConfiguration 就是从classpath中搜寻META-INF/spring.factories 配置文件,并将其中的的org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射,加载到容器中.
总结
Spring Boot底层实现自动装配的步骤是:
- 程序启动
- @SpringBootApplication 起作用
- @EnableAutoConfiguration
- @AutoConfigurationPackage 主要是@Import(AutoConfigurationPackages.Registrar.class) 通过将Registrar 类导入容器中,而Registrar 主要作用是将主配置类同级目录及子包,并将相应的组件导入到容器中
- @Import(AutoConfigurationImportSelector.class) 它通过将AutoConfigurationImportSelector 导入容器中, AutoConfigurationImportSelector 通过selectImports 方法的执行, 会将内部工具类SpringFactoriesLoader, 查找classpath上所有jar包中的META-INF/spring.factories 惊醒加载,并通过反射将配置类夹给SpringFactory 加载器进行一系列的容器创建过程
执行原理
下面我们查看run()方法内部的源码,核心代码具体如下:
1 | public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { |
从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实 例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下:
实例初始化
1 | "unchecked", "rawtypes" }) ({ |
从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。
- (1) this.webApplicationType = WebApplicationType.deduceFromClasspath()
用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路 径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传 统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
- (2) this.setInitializers(this.getSpringFactorieslnstances(ApplicationContextlnitializer.class))
用于Spr ingApplication应用的初始化器设置。在初始化器设置过程中,会使用Sp ring类加载器
Spr ingFacto riesLoade r 从 META-INF/sp ring .facto ries 类路径下的 META-INF 下的 spr ing .facto res 文件中 获取所有可用的应用初始化器类ApplicationContextlnitialize r。
- (3) this.setListeners(this.getSpringFactorieslnstances(ApplicationListener.class))
用于Spr ingApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样, 也是使用 Spr ingFacto riesLoade r 从 META-INF/sp ring .facto ries 类路径下的 META-INF 下的 spr ing .facto res文件中获取所有可用的监听器类ApplicationListene r。
- (4) this.mainApplicationClass = this.deduceMainApplicationClass()
用于推断并设置项目main()方法启动的主程序启动类
项目初始化
分析完(new Spr ingApplication(p rimarySou rces)) .run()(args)源码前一部分 Spr ingApplication 实例对象 的初始化创建后,查看r un(a rgs)方法执行的项目初始化启动过程,核心代码具体如下:
1 | public ConfigurableApplicationContext run(String... args) { |
从上述源码可以看出,项目初始化启动过程大致包括以下部分:
第一步:获取并启动监听器
this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication 实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。
第二步:根据SpringApplicationRunListeners以及参数来准备环境
this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境 进行预设置,同时通过this.configurelgnoreBeanlnfo(environment)方法排除一些不需要的运行环境
第三步:创建Spring容器
根据webApplicationType进行判断,确定容器类型,如果该类型为SERVLET类型,会通过反射装载对 应的字节码,也就是 A nnotationConfigServletWebServerApplicationContext,接着使用之前初 始化设置的context (应用上下文环境)、environment (项目运行环境)、listeners (运行监听 器)、applicationArguments (项目参数)和printedBanner (项目图标信息)进行应用上下文的组 装配置,并刷新配置
第四步:Spring容器前置处理
这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操 作:将启动类注入容器,为后续开启自动化配置奠定基础
第五步:刷新容器
开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bea n资源的定位,解析,注册等 等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭
第六步:Spring容器后置处理
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启 动结束log,或者一些其它后置处理。
第七步:发出结束执行的事件
获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进 去了,创建—ApplicationStartedEvent 事件,并执行 ConfigurableApplicationContext 的 publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布 事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。
第八步:执行Runners
用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中, Spring Boot提供的执行器接口有A pplicationRunner和CommandLineRunner两种,在使用时只需 要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会 立即执行这些特定程序