SpringBoot 自动装配流程及源码剖析

109 阅读6分钟

Spring Framework 一直在致力于解决一个问题,就是如何让 bean 管理变得更简单,如何让开发者尽可能的少关注一些基础化的 bean 配置,从而实现自动装配。所谓的自动装配,实际上就是如何自动将 bean 装载到 IOC 容器中来

实际上在 spring 3.x 版本中,Enable 模块驱动注解的出现,已经有了一定的自动装配的雏形,而真正能够实现这一机制,还是在 spirng 4.x 版本中,conditional 条件注解的出现;@EnableXxx 注解其实本质上就是 @Import 注解的体现,而 @Import 注解是为了替代之前的 <import> 标签而出现的.

@Import 可以根据添加的不同类型来作出不一样的操作

  • 普通类型:直接注入该类型的对象
  • 实现了 ImportBeanDefinitionRegistrar 接口:不注入该类型的对象,调用 registerBeanDefinitions 方法,通过注册器进行注入
  • 实现了 ImportSelector 接口:不注入该类型的对象,调用 selectImports 方法,将返回的数据注入到容器中

深入分析装配过程

启动类注解:@SpringBootApplication->内置注解:@EnableAutoConfiguration

EnableAutoConfiguration:主要作用就是帮助 SpringBoot 应用把所有符合条件的 @Configuration 配置都加载到当前创建且使用的 IOC 容器中

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

如上可以看到,通过 Import 导入的类型不仅仅是一个普通的配置类,而是一个实现 ImportSelector 接口的类型,它基于动态 bean 加载的功能;由于其接口下的核心方法是 selectImports,可以追踪一下其源码实现

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	// 从配置文件(spring-autoconfigure-metadata.properties)中加载
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	// 获取所有候选配置类 EnableAutoConfiguration
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
			annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	// 获取元注解中的属性
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	// 使用 SpringFactoriesLoader 加载 classpath 路径下 META-INF\spring.factories中,
	// key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	// 去重
	configurations = removeDuplicates(configurations);
	// 应用 exclusion 属性
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	// 过滤,检查候选配置类上的注解 @ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
	configurations = filter(configurations, autoConfigurationMetadata);
	// 广播事件
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}

本质上来说,其实 EnableAutoConfiguration 会帮助 SpringBoot 应用把所有符合 @Configuration 配置都加载到当前 SpringBoot 创建的 IOC 容器,而这里面借助了 Spring 框架提供的一个工具类 SpringFactoriesLoader 的支持;以及用到了 Spring 提供的条件注解 @Conditional,选择性的针对需要加载的 bean 进行条件过滤

SpringFactoriesLoader 其实和 SPI 实现机制的是一样的,只不过它不会像 SPI 一次性把所有的类全部加载完,而是通过 key「全限定类名」加载其对应 value,加载的文件名称:classpath:META-INF/spring.factories

条件过滤配置类

在 spring.factories 文件中配置的类有很多,有时候配置类又要依赖于其他的类型存在才得以生存,所以在 SpringBoot 自动装配中还提供了 ConditionalOnClass、ConditionalOnBean 这些条件来过滤所加载的配置类,摘取部分 spring-boot-autoconfigure 模块下 spring-autoconfigure-metadata.properties 文件源码

org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration=
org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration.ConditionalOnClass=io.lettuce.core.RedisClient
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration=
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations

通过这种条件过滤可以有效的减少 @Configuration 类数量从而降低 SpringBoot 启动时间

小结

通过启动类注解下自动配置注解实现的 ImportSelector 选择器,去扫描指定文件下所有的配置类,按照系统需要去加载和过滤相关的配置类,满足系统运行其他中间件的需要,通过以下图来小结上面所介绍的内容,接下来就是介绍容器是在那个过程中去执行扫描工作的

深入解析过程

以上内容只是介绍配置类是如何装配进去的,具体的解析还是交由我们容器去处理的,而且这些配置类也不是说在拿到以后就直接去注入的,它们是等待所依赖的类型先注入以后,到最后才去处理的.

通过类图可以看出,@Import 导入的选择器并不是直接实现 ImportSelector 接口,而是实现的 DeferredImportSelector,其字面含义就是延迟导入,对父接口做了增强处理

DeferredImportSelector 接口

通过以上的类结构可以看出 DeferredImportSelector 接口是基于 ImportSelector 接口的一个扩展

DeferredImportSelector 接口本身也有 ImportSelector 接口的功能,如果我们仅仅是实现了DeferredImportSelector 接口,重写了 selectImports 方法,那么 selectImports 方法还是会被执行的,来看代码

public class MyImportSelector implements DeferredImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		System.out.println("MyImportSelector implements DeferredImportSelector >>>");
		return new String[0];
	}
}
@Configuration
@Import(MyImportSelector.class)
public class MyAutoConfig {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyAutoConfig.class);
	}
}

但是当我们重写了 DeferredImportSelector 中的 Group 接口,并重写了 getImportGroup 方法,那么容器在启动时就不会执行 selectImports 方法了,而是执行 getImportGroup 方法,进而执行 Group 接口中重写的方法,代码如下:

public class MyImportSelector implements DeferredImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		System.out.println("MyImportSelector implements DeferredImportSelector >>>");
		return new String[0];
	}

	@Override
	public Class<? extends Group> getImportGroup() {
		System.out.println("MyImportSelector#getImportGroup");
		return MyGroup.class;
	}

	public static class MyGroup implements Group {
		private List<Entry> imports = new ArrayList<>();

		@Override
		public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
			System.out.println("MyImportSelector->MyGroup#process");
		}

		@Override
		public Iterable<Entry> selectImports() {
			System.out.println("MyImportSelector->MyGroup#selectImports");
			return imports;
		}
	}
}

ImportSelector 实例的 selectImports 方法的执行时机,是在 @Configuration 注解中的其他逻辑被处理之前,所谓的其他逻辑,包括对 @ImportResource、@Bean 这些注解的处理(注意,这里只是对 @Bean 修饰方法的处理,并不是立即调用 @Bean 修饰的方法,这个区别很重要!)

上面的结论以及从流程图分析,我们可以直接在源码中找到到对应的答案,首先定位到 ConfigurationClassParser#parse 方法

  • 首先看到调用的是 doProcessConfigurationClass:循环遍历每一个处理配置类
  • 处理 @Import 注解方法中,在这个方法可以看到 @Import 注解的实现逻辑,处理 ImportSelector 接口、子接口不同类型的实现:DeferredImportSelector、ImportSelector,在处理前者时将对应的实例存储了起来
  • 等待其他的配置类都已经处理完成以后,到 parse 方法块后面,执行 deferredImportSelectorHandler#process 方法
    • 先处理的是 register 方法,获取我们重写的 importGroup 方法的返回值,如果为空说明没有重写 Group 接口,那么就使用原来的 ImportSelector 实现类对象且创建默认的 Group 实现 DefaultDeferredImportSelectorGroup,否则就是使用自定义的 Group 对象
    • 再看 processGroupImports 方法,主要看的是方法块里面的 grouping.getImports 方法,在这里面会根据 Group 实现类的不同来执行 process 方法,如果是默认实现,那么调用的就是 ImportSelector 实现类的 selectImports 方法返回,否则就调用自定义 Group 对象的 process 方法,在这里面会看到 getAutoConfigurationEntry 装配的核心方法被调用,返回自动装配的那些配置类
    • 到这里,就可以清晰的了解到自动装配里面核心的解析过程是什么的!!!

同时,从以上可以看出,ImportSelector 与 DeferredImportSelector 的区别,就是执行 selectImports 方法时有所区别,这个差别期间,Spring 容器对此 Configuration 配置类做了其他的逻辑:包括 @ImportResource、@Bean 这些注解处理

最后,在解析自动装配的过程中涉及到的比较重要的类 ConfigurationClassPostProcessor,它既实现了 BeanDefinitionRegisterPostProcessor 同时也实现了 BeanFactoryPostProcessor,这个类后面会单独写一篇文章来对其里面的核心处理过程分析。

更多技术文章可以查看:vnjohn 个人博客