一、前言
本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。
借 SpringBoot学习之自动装配 中一句话:
SpringBoot并不属于一种新的技术,只不过Spring-Boot-Starter-xxxx的启动器帮我们配置了若干个被Spring管理的bean,当我们的项目依赖这些jar并启动Spring应用时,Spring的Container容器已经把jar包下的对象加以创建及管理了。
在 Spring源码分析十:SpringBoot中Mybatis的自动化配置 中我们解析了MyBatis 的自动化配置流程,其中我们提到MyBatis 通过自动装配的方式启用功能。本篇我们就分析一下自动装配的过程。
本文就是来分析Springboot自动装配的源码实现。
1. ImportSelector
之所以需要了解这个接口是因为下面的讲解离不开这个接口。
ImportSelector
见名知意,是一种引入选择器。其中selectImports 方法返回的String[] 数组的元素是类的全路径名,Spring 会调用 selectImports, 并按照其返回的结果数组的元素指向的类加载到Spring容器中。
public interface ImportSelector {
// 返回值将会被认为是类的全路径名,Spring通过反射将其返回的类路径加载到容器中
String[] selectImports(AnnotationMetadata importingClassMetadata);
// 获取排除过滤器
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
关于 ImportSelector
的调用处理实际上是在 ConfigurationClassPostProcessor
中。关于 ConfigurationClassPostProcessor
的分析已经开设了文章,具体请看:
Spring 源码分析衍生篇七 :ConfigurationClassPostProcessor 上篇
2. DeferredImportSelector
DeferredImportSelector
是 ImportSelector
接口的子接口。
DeferredImportSelector
有两个特点:
- 继承该接口的 ImportSelector会在最后执行。这一点是因为在
ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
方法中直到解析出来其他的候选配置类才会调用this.deferredImportSelectorHandler.process();
来解析DeferredImportSelector
- 如果定义了一个以上的DeferredImportSelector则使用Order接口来进行排序。这一点也是在
this.deferredImportSelectorHandler.process();
中进行了排序调用。
3. spring.factories
Springboot 强调约定大于配置,其中有一个约定就是 springboot启动的时候会加载 META-INF/spring.factories 文件。一般来说 spring.factories 都是 key=value
,key 是某一个类的全路径名,value 是一个或多个类的全路径名。
如下:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
4. spring-autoconfigure-metadata.properties
spring-autoconfigure-metadata.properties 和 spring.factories 类似,都是在 META-INF 目录下的文件。spring-autoconfigure-metadata.properties 的格式为 key.自动装配条件=value
。与spring.factories 不同的是 spring-autoconfigure-metadata.properties 管理的是 Bean的装配条件。
假设有A,B两个类需要自动装配,但是B必须要在A装配后才能装配。我们仅仅spring.factories 是无法控制Bean的加载顺序,此时可以通过
// com.kingfish.B、com.kingfish.A 为全路径名,AutoConfigureAfter 为装配条件
// 即 B 需要在 A装配后进行装配
com.kingfish.B.AutoConfigureAfter = com.kingfish.A
即简单来说
spring.factories
控制哪些Bean进行装配spring-autoconfigure-metadata.properties
控制Bean 装配的条件
三、源码解析
1. 原理概述
要分析Springboot自动化配置的原理,首先我们需要看他的启动方式如下。下面的启动方式基本就是Springboot的固定启动方式。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
1.1 @EnableAutoConfiguration
这时候我们进入 @SpringBootApplication
注解,发现 @SpringBootApplication
注解上赫然写着 @EnableAutoConfiguration
注解,我们再进去 @EnableAutoConfiguration
查看。这里我们发现 @EnableAutoConfiguration
注解通过 @Import(AutoConfigurationImportSelector.class)
引入了一个类AutoConfigurationImportSelector
。
1.2 AutoConfigurationImportSelector
AutoConfigurationImportSelector
实现了 DeferredImportSelector
接口
在 Spring 源码分析补充篇一 :DeferredImportSelector 的处理 中我们介绍了 DeferredImportSelector
的处理流程 : DeferredImportSelector 的处理过程并非是直接 调用ImportSelector#selectImports方法。而是调用 DeferredImportSelector.Group#process 和 Group#selectImports 方法来完成引入功能。
所以下面我们直接来看 AutoConfigurationImportSelector.AutoConfigurationGroup
中的实现
1.2.1 AutoConfigurationGroup#process
// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// getAutoConfigurationMetadata() 方法读取并解析了spring-autoconfigure-metadata.properties 文件
// AutoConfigurationEntry 包含,需要自动装配的类和需要排除的类
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
// 添加到 AutoConfigurationEntry集合中等待加载
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 将需要加载的 类添加到 entries 集合中,key 是 需要引入的类,value 是引入该类的类的一些信息
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
其中 (AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry
的实现如下
/**
* org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 判断是否启动自动装配
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取 @SpringBootApplication 的注解属性exclude、excludeName。
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取未筛选前的配置类。这里读取了spring.factories 文件的内容
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的配置类
configurations = removeDuplicates(configurations);
// 获取 需要排除的 配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查需要排除的配置类
checkExcludedClasses(configurations, exclusions);
// 移除需要排除的配置类
configurations.removeAll(exclusions);
// 根据 spring-autoconfigure-metadata.properties 中的装配条件进行过滤
configurations = filter(configurations, autoConfigurationMetadata);
// 给 AutoConfigurationImportListener 发布一个onAutoConfigurationImportEvent事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// 封装成一个 AutoConfigurationEntry 返回,其中包含,需要自动装配的类(configurations) 和需要排除的类 (exclusions)
return new AutoConfigurationEntry(configurations, exclusions);
}
// 这里我们看到可以通过设置 EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY 属性来 控制是否开启自动化配置
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
// 加载了 Spring.factories 文件的内容
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass() 返回的类型是 EnableAutoConfiguration.class。
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
下面强调几点 :
- getCandidateConfigurations :这个方法是去加载
META-INF/spring.factories
文件的。另外我们又发现getSpringFactoriesLoaderFactoryClass
返回的就是EnableAutoConfiguration.class
。也即是说,在这一步,Spring以EnableAutoConfiguration.class
的全路径名字作为key 去获取META-INF/spring.factories
中对应的value。 - filter(configurations, autoConfigurationMetadata) :autoConfigurationMetadata 中保存的是
spring-autoconfigure-metadata.properties
解析出来的内容。filter 根据此内容对配置类进行了一个过滤
1.2.2 AutoConfigurationGroup#selectImports
在 AutoConfigurationGroup#process
中我们知道了Spring将需要装配的类缓存到了 autoConfigurationEntries
和 entries
中
// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports
@Override
public Iterable<Entry> selectImports() {
// 如果没有需要装配的类至极返回空集合
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// 获取所有需要排除的 类集合
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
// 获取所有需要装配的类集合
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// 移除所有排除类
processedConfigurations.removeAll(allExclusions);
// 将需要加载的类排序返回,排序规则按照 spring-autoconfigure-metadata.properties 中指定的顺序
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
---------------======================
再看一下 SpringFactoriesLoader.loadFactoryNames
方法的实现。这里可以看到loadFactoryNames
方法其实上是根据类的全路径类名去 spring.factories
中获取值。这里更细致的代码就不具体分析了
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
1.3 配置文件
1.3.1 spring.factories
我们这里找到 META-INF/spring.factories
看一下。可以看到都是 key-value形式,并且可以是一些注解的全路径名,value是需要加载的配置类。
1.3.2 spring-autoconfigure-metadata.properties
2. 总结
结合上述:
- 首先需要创建
META-INF/spring.factories
文件,文件中的存储是key-value形式 @SpringBootApplication
注解 继承了@EnableAutoConfiguration 注解
。而@EnableAutoConfiguration
注解中引入了AutoConfigurationImportSelector
类。AutoConfigurationImportSelector
类会根据@EnableAutoConfiguration
注解的全路径类名作为 key值加载META-INF/spring.factories
文件 的value值,value值是预先配置好的,即当 使用@EnableAutoConfiguration
注解开启自动装配功能时就会加载对应value 所指向的配置类。(如果我们想定义扫描别的key,就可以模仿AutoConfigurationImportSelector
来实现 )- 将value值对应的配置类信息返回,并作为
AutoConfigurationImportSelector#selectImports
方法的返回值返回。 - Springboot会根据 selectImports 的返回值中的配置类全路径名加载对应的配置类。这些配置类再完成相应的工作实现自动装配功能。
简单来说,Spring通过 @EnableXXX 注解来启用某项功能的原理是通过 @EnableXXX 上的@Import 注解来引入 自定义的ImportSelector实现类,并在其 selectImports 方法中将该功能需要使用的类注入到Spring容器中,从而达到启用某项功能。
额外的,如果想要在Spring引入 jar 时就启用功能(比如MyBatis)。可以在 spring.factories 文件中以org.springframework.boot.autoconfigure.EnableAutoConfiguration
为key,以需要引入的类的全路径名作为value。
因为Spring 启动时会扫描所有的spring.factories 文件中的key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration
的值并将其value通过反射注入到容器中。
下图为MyBatis 的spring.factories文件内容。
四、关于ImportSelector的简陋Demo
1. EnableDemo
自定义注解 @EnableDemo
,作为启用注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class)
public @interface EnableDemo {
}
2. DemoImportSelector
看注释看注释。。。
public class DemoImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 也可以在 META-INF/spring.factories 配置加载类并获取返回
// 返回类的全路径地址,会被Spring按照路径加载到容器中
return new String[]{"com.kingfish.auto.Demo"};
}
}
3. Demo
public class Demo {
public void getmsg(){
System.out.println("Demo.getmsg");
}
}
4. DemoSpringRunner
@Component
public class DemoSpringRunner implements ApplicationRunner {
@Autowired
private Demo demo;
@Override
public void run(ApplicationArguments args) throws Exception {
demo.getmsg();
}
}
4. 测试
上面可以看到,我们并没有使用常规的注入方式将 Demo类注入到容器中。我们启动测试。可以看到Demo正常加载并输出。大功告成!
以上:内容部分参考
《Spring源码深度解析》、
https://blog.csdn.net/boling_cavalry/article/details/82555352
https://www.jianshu.com/p/480ebb1ecc8b
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正