一、前言
本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。
在使用SpringBoot的时候,我们引入其他功能的时候通过一个简单的@EnableXXX 注解就可实现比如
@EnableAspectJAutoProxy(proxyTargetClass = true)
: 启用 AOP。实际上只要引入了aop的包就会启动aop功能,这是因为aop默认引入了该注解。@EnableTransactionManagement
: 启用事务
本文就是来分析Springboot自动装配的源码实现。
1. ImportSelector
之所以需要了解这个接口是因为下面的讲解离不开这个接口。
ImportSelector 见名知意,是一种引入选择器。其中selectImports 方法返回的String[] 数组的元素是类的全路径名,Spring 会调用 selectImports, 并按照其返回的结果数组的元素指向的类加载到Spring容器中。
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for excluding classes from the import candidates, to be
* transitively applied to all classes found through this selector's imports.
* <p>If this predicate returns {@code true} for a given fully-qualified
* class name, said class will not be considered as an imported configuration
* class, bypassing class file loading as well as metadata introspection.
* @return the filter predicate for fully-qualified candidate class names
* of transitively imported configuration classes, or {@code null} if none
* @since 5.2.4
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
具体的调用链路比较多,这里就不具体分析,调用逻辑在 ConfigurationClassParser#processImports
中,见下图。我们只需要知道Springboot 在启动时候会加载引入(使用 @Import 注解引入)的 ImportSelector
实现类。但是需要注意的是,Spring自动装配的类AutoConfigurationImportSelector
实现的接口并不是ImportSelector
接口,而是DeferredImportSelector
接口。
注: 关于 ImportSelector 的调用处理实际上是在 ConfigurationClassPostProcessor 中。关于 ConfigurationClassPostProcessor 的分析已经开设了文章,具体请看:
Spring 源码分析衍生篇七 :ConfigurationClassPostProcessor 上篇
2. DeferredImportSelector
AutoConfigurationImportSelector
实现的是 DeferredImportSelector
接口。
DeferredImportSelector
是 ImportSelector
接口的子接口。
DeferredImportSelector
有两个特点:
- 继承该接口的 ImportSelector会在最后执行
- 如果定义了一个以上的DeferredImportSelector则使用Order接口来进行排序
3. spring.factories
这个仅需要知道,Springboot 强调约定大于配置,其中有一个约定就是 springboot启动的时候会加载 META-INF/spring.factories 文件,至于有什么用,后面会讲到。
三、源码解析
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
接口,DeferredImportSelector
又是ImportSelector的子接口,这里需要注意的是 AutoConfigurationImportSelector
由于实现的是 DeferredImportSelector
接口,所以在调用逻辑上并不会调用 AutoConfigurationImportSelector.selectImports
方法(后面会具体讲解)。下面我们先来详细分析一下 getAutoConfigurationEntry
方法。
我们来看 AutoConfigurationImportSelector
的部分代码如下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
/**
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param autoConfigurationMetadata the auto-configuration metadata
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
/**
* 我们主要看这个方法
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 判断是否启动自动装配
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取 @SpringBootApplication 的注解属性exclude、excludeName。
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取未筛选前的配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的配置类
configurations = removeDuplicates(configurations);
// 获取 需要排除的 配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查需要排除的配置类
checkExcludedClasses(configurations, exclusions);
// 移除需要排除的配置类
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
// 发布一个事件
fireAutoConfigurationImportEvents(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;
}
上面的代码中 在 getAutoConfigurationEntry
方法中 还有一个关键方法 getCandidateConfigurations
。我们再来看一下这个方法。这个方法我们仅仅通过他的断言我们就可以发现,这个方法是去加载 META-INF/spring.factories
文件的。另外我们又发现 getSpringFactoriesLoaderFactoryClass
返回的就是 EnableAutoConfiguration.class
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
再看一下 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 META-INF/spring.factories
我们这里找到 META-INF/spring.factories
看一下。可以看到都是 key-value形式,并且可以是一些注解的全路径名,value是需要加载的配置类。
1.4 DeferredImportSelector 的问题
经过上面的分析,我们可以确定 getAutoConfigurationEntry
方法就是整个解析的核心。那么上面为什么说并不会调用 AutoConfigurationImportSelector.selectImports
方法呢 ?
这里我们着重观察一下 ConfigurationClassParser#processImports
方法,
调用链路如下图
代码分析如下(这里代码分析源自 https://www.jianshu.com/p/480ebb1ecc8b):
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
//检查包含有Import注解的集合是不是空的,空的则表示没有
if (importCandidates.isEmpty()) {
return;
}
//检查是否存在循环引入,通过deque的方式来检查
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
//将当前bean放到importStack中,用于检查循环引入
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//import指定的Bean是ImportSelector类型
if (candidate.isAssignable(ImportSelector.class)) {
//实例化指定的ImportSelector子类
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
//如果是ImportSelector的子类DeferredImportSelector的子类则按照DeferredImportSelectorHandler逻辑进行处理
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
//如果不是则获取指定的需要引入的class的ClassNames
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
//根据ClassNames获取并封装成一个SourceClass
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
//继续调用processImports处理
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}//如果是ImportBeanDefinitionRegistrar的子类
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
//如果对应的ImportBeanDefinitionRegistrar子类对象,并放到configClass,这个是用来注册额外的bean的
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
//不是上面任何类的子类就可以进行处理了,将指定的需要引入的bean转化为ConfigurationClass,然后到processConfigurationClass方法中处理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
这里我们发现一个问题
我们发现,上面在讲ImportSelector
接口的时候,我们明明说他们只有调用了580行的 selector.selectImports(currentSourceClass.getMetadata());
语句才能而调用 ImportSelector
接口的 selectImports
方法。
而 AutoConfigurationImportSelector
实现的接口是 DeferredImportSelector
接口,所以这里他会执行if分支的内容,也就是 this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
语句。
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)
里面的代码就不具体贴出来了,在handle方法中关键代码是 handler.processGroupImports();
再继续追溯 发现 grouping.getImports()
。下面贴出 getImports()
代码如下:
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
这里我们可以看到group调用了两个方法 process和 selectImports(这两个方法都是 AutoConfigurationImportSelector.AutoConfigurationGroup 的方法),如下:
- 我们可以看到 process 方法中调用了getAutoConfigurationEntry 方法,通过上面的解释我们知道 getAutoConfigurationEntry 方法就是用来解析spring.factories 文件的。另外getAutoConfigurationEntry 方法的返回值保存在了 autoConfigurationEntries中。
- selectImports 方法则是对 autoConfigurationEntries 结果进行进一步筛选排序,最终返回。
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 省略断言,在这里我们可以看到他调用了 getAutoConfigurationEntry 方法。通过上面的解释我们知道 getAutoConfigurationEntry 方法就是用来解析spring.factories 文件的。
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
@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);
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
所以AutoConfigurationImportSelector
并不会调用方法
AutoConfigurationImportSelector.#selectImports
。而是会调用AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports
。注意二者的区别, AutoConfigurationGroup
是 AutoConfigurationImportSelector
一个静态内部类。
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
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正