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 个人博客