背景
当我需要在一个Configuration配置类中导入另一个Configuration配置类时,我们可以使用 @Import注解 或者 @ImportAutoConfiguration ,如下两图:
经过试验证明,从效果上来看,这两种方式都能很好的工作,额外的配置类都被正确的加载了,并且都能触发 ImportAware 扩展点,那么这两个注解到底有啥区别呢?
解析
针对背景中的问题,我在网上搜索良久都没有得到明确的满意答案,还是决定通过源码来窥探一下其中奥秘。
如果时间紧任务急的同学也可以直接看最后的总结
原理剖析
通过两个注解的元信息,我们可以看到@ImportAutoConfiguration 注解中继承了一个@Import 注解。
所以想要搞清楚这两者的区别,我们必须先搞明白 Import 注解是个什么东西。
@Import
先贴一个Import注解类的注释信息 (附上有道翻译版本。。)
表示要导入的一个或多个@Configuration类。
提供与Spring XML中的<import/>元素等价的功能。允许导入@Configuration类、ImportSelector和ImportBeanDefinitionRegistrar实现,以及常规的组件类(从4.2;类似于AnnotationConfigApplicationContext.register)。
在导入的@Configuration类中声明的@Bean定义应该通过@Autowired注入来访问。可以自动连接bean本身,也可以自动连接声明该bean的配置类实例。后一种方法允许在@Configuration类方法之间进行显式的、ide友好的导航。
可以在类级别声明,也可以作为元注释声明。
如果需要导入XML或其他non-@Configuration bean定义资源,则使用@ImportResource注释。
大概意思就是说,Import 注解主要是用来替代 Spring XML 中的<import/>标签的,我们来看下<import/>标签又是干嘛的呢?
根据官方文档的说法,import是用来解决配置混乱的问题的,它支持我们将需要声明的bean进行分类并写入多个xml配置文件中,并通过import标签将这些配置文件整合到一起,是配置文件逻辑更加清晰,也更加优雅。
大家知道@Configuration类本质上就是一个xml配置文件的替代品,现在我们知道了,@Import 注解的初衷就是使我们可以在配置类中导入其他配置类,并最终将这些配置类整合在一起进行处理。
除此之外,@Import注解的注释中写了,除了导入@Configuration配置类以外,它还能导入ImportSelector 和 ImportBeanDefinitionRegistrar 实现类,那么 ImportSelector和ImportBeanDefinitionRegistrar 是什么东西呢。
正如上面图片所示的那样,直接在 Import 注解中声明配置类的方式是最常用的姿势,不过这是有缺陷的,比如:
- 导入的配置类集合是固定的,无法根据实际场景进行选择性的导入
- 由于Import注解的value只接受 Class 类型的,意味着所导入的类必须在当前模块或所依赖的下游模块中,无法导入一个上游 或 第三方的配置类(类似SPI的机制,虽然这不是很好理解,也很少会有这种需求。。。)
ImportSelector 就可以解决上面的两个缺陷,它根据导入的ImportSelector实现类所返回的值作为需要导入的 配置类集合,并对这些配置类进行加载。
我们考虑这样一个需求
如果应用配置中指定了使用1号配置类时,加载 MyOtherConfiguration1.java,否则默认加载 MyOtherConfiguration2.java
针对这个场景,我们可以通过以下配置方式进行实现:
相比 ImportSelector 这种返回类全限定名集合来指定需要加载的配置类的方式,ImportBeanDefinitionRegistrar 则更加直接,它的接口中额外提供了 BeanDefinitionRegistry 参数,意味着我们可以在 ImportBeanDefinitionRegistrar 的实现类中自主的进行 Bean的注册,而不是返回一个类名或 类对象。
我们依然考虑这样一个需求
如果应用配置中指定了使用1号配置类时,加载 MyOtherConfiguration1.java,否则默认加载 MyOtherConfiguration2.java
针对这个场景,我们可以通过以下配置方式进行实现:
至此,我们算是搞清楚了 @Import 注解的工作模式,关于它的工作机制与原理,如果大家感兴趣,可以对源码进行窥探,相关逻辑入口在 org.springframework.context.annotation.ConfigurationClassParser:302行
@ImportAutoConfiguration
接下来轮到 @ImportAutoConfiguration 注解了
定睛一看,@ImportAutoConfiguration 继承了 @Import 注解,并导入了一个 ImportSelector 的实现类 ImportAutoConfigurationImportSelector。
根据我们刚才的了解
Import注解 根据导入的ImportSelector实现类所返回的值作为需要导入的 配置类集合,并对这些配置类进行加载。
我们可以猜到 ImportAutoConfigurationImportSelector 肯定是根据某种逻辑返回了需要被导入的配置类集合。
我们发现 ImportAutoConfigurationImportSelector 并没有实现 selectImports 接口,这个接口是由它的父类 AutoConfigurationImportSelector 进行实现的。
可以看到它是调用 getAutoConfigurationEntry 获取配置类集合,继续看 getAutoConfigurationEntry 的逻辑
可以看到它调用了 getCandidateConfigurations 方法获取的配置类集合,就是这个方法被 ImportAutoConfigurationImportSelector 重写了逻辑,如下:
简单解释一下它的逻辑
- 先获取当前配置类上的 ImportAutoConfiguration 注解信息
- 如果 ImportAutoConfiguration 注解中指定了classes(或value) 属性值,那么直接返回 classes 的值
- 如果未配置 classes(或value) 属性值,从 spring.factories 文件中读取key为当前配置类名的结果
配合一个案例场景可能更加好理解
在 MyAutoConfiguration 配置类中 导入 MyOtherAutoConfiguration
通过 @ImportAutoConfiguration 注解可以有两种方式实现这个需求
方式一:直接在注解中指定
方式二:注解中不指定classes或value,并在 spring.factories 中指定目标配置类
总结
相同点
- Import 与 ImportAutoConfiguration 注解都可以通过 value 属性指定需要导入的@Configuration配置类
- Import 与 ImportAutoConfiguration 注解都支持通过 value 属性配置 ImportSelector类、ImportBeanDefinitionRegistrar类 的方式来间接导入所需的配置类
不同点
- @Import 注解只能通过 value 属性值进行导入
- @ImportAutoConfiguration 注解的 value 属性值是可选的,当value属性值未指定时,会尝试从 spring.factories 文件中读取相应的配置作为需要导入的配置类集合
彩蛋
你知道 @ImportAutoConfiguration 和 @EnableAutoConfiguration 有什么区别吗?
可以看到很熟悉的一幕,@EnableAutoConfiguration注解继承了 @Import 注解并加载了一个
AutoConfigurationImportSelector,这不就是刚刚 ImportAutoConfigurationImportSelector 的父类吗?
我们看下没有被 ImportAutoConfigurationImportSelector 重写过的逻辑是怎么样的
它直接去 spring.factories 配置文件中寻找 EnableAutoConfiguration 配置项来作为配置类集合进行加载,如下:
那 @EnableAutoConfiguration 在哪里被使用的呢?
@SpringBootApplication 注解继承了 EnableAutoConfiguration 注解。。。
那么。。好像发现了 SpringBoot 自动装配的秘密呢 ~~