Spring源码分析十一:Springboot 自动装配

一、前言

本文是笔者阅读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

DeferredImportSelectorImportSelector 接口的子接口。

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将需要装配的类缓存到了 autoConfigurationEntriesentries

	// 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. 总结

结合上述:

  1. 首先需要创建 META-INF/spring.factories 文件,文件中的存储是key-value形式
  2. @SpringBootApplication 注解 继承了 @EnableAutoConfiguration 注解。而 @EnableAutoConfiguration 注解中引入了 AutoConfigurationImportSelector 类。
  3. AutoConfigurationImportSelector 类会根据 @EnableAutoConfiguration 注解的全路径类名作为 key值加载 META-INF/spring.factories 文件 的value值,value值是预先配置好的,即当 使用 @EnableAutoConfiguration 注解开启自动装配功能时就会加载对应value 所指向的配置类。(如果我们想定义扫描别的key,就可以模仿 AutoConfigurationImportSelector 来实现 )
  4. 将value值对应的配置类信息返回,并作为 AutoConfigurationImportSelector#selectImports 方法的返回值返回。
  5. 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
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值