背景

当我需要在一个Configuration配置类中导入另一个Configuration配置类时,我们可以使用 @Import注解 或者 @ImportAutoConfiguration ,如下两图:

import curses 安装 import the configuration_import curses 安装

import curses 安装 import the configuration_加载_02

经过试验证明,从效果上来看,这两种方式都能很好的工作,额外的配置类都被正确的加载了,并且都能触发 ImportAware 扩展点,那么这两个注解到底有啥区别呢?

 

解析

针对背景中的问题,我在网上搜索良久都没有得到明确的满意答案,还是决定通过源码来窥探一下其中奥秘。

如果时间紧任务急的同学也可以直接看最后的总结

原理剖析

import curses 安装 import the configuration_spring_03

import curses 安装 import the configuration_加载_04

通过两个注解的元信息,我们可以看到@ImportAutoConfiguration 注解中继承了一个@Import 注解。

 

所以想要搞清楚这两者的区别,我们必须先搞明白 Import 注解是个什么东西。

 

@Import

先贴一个Import注解类的注释信息 (附上有道翻译版本。。)

import curses 安装 import the configuration_属性值_05

表示要导入的一个或多个@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 curses 安装 import the configuration_import curses 安装_06

根据官方文档的说法,import是用来解决配置混乱的问题的,它支持我们将需要声明的bean进行分类并写入多个xml配置文件中,并通过import标签将这些配置文件整合到一起,是配置文件逻辑更加清晰,也更加优雅。

 

大家知道@Configuration类本质上就是一个xml配置文件的替代品,现在我们知道了,@Import 注解的初衷就是使我们可以在配置类中导入其他配置类,并最终将这些配置类整合在一起进行处理。

import curses 安装 import the configuration_import curses 安装_07

 

除此之外,@Import注解的注释中写了,除了导入@Configuration配置类以外,它还能导入ImportSelector 和 ImportBeanDefinitionRegistrar 实现类,那么 ImportSelector和ImportBeanDefinitionRegistrar 是什么东西呢。

 

正如上面图片所示的那样,直接在 Import 注解中声明配置类的方式是最常用的姿势,不过这是有缺陷的,比如:

  1. 导入的配置类集合是固定的,无法根据实际场景进行选择性的导入
  2. 由于Import注解的value只接受 Class 类型的,意味着所导入的类必须在当前模块或所依赖的下游模块中,无法导入一个上游 或 第三方的配置类(类似SPI的机制,虽然这不是很好理解,也很少会有这种需求。。。)

 

ImportSelector 就可以解决上面的两个缺陷,它根据导入的ImportSelector实现类所返回的值作为需要导入的 配置类集合,并对这些配置类进行加载。

我们考虑这样一个需求

如果应用配置中指定了使用1号配置类时,加载 MyOtherConfiguration1.java,否则默认加载 MyOtherConfiguration2.java

针对这个场景,我们可以通过以下配置方式进行实现:

import curses 安装 import the configuration_加载_08

import curses 安装 import the configuration_spring_09

 

相比 ImportSelector 这种返回类全限定名集合来指定需要加载的配置类的方式,ImportBeanDefinitionRegistrar 则更加直接,它的接口中额外提供了 BeanDefinitionRegistry 参数,意味着我们可以在 ImportBeanDefinitionRegistrar 的实现类中自主的进行 Bean的注册,而不是返回一个类名或 类对象。

我们依然考虑这样一个需求

如果应用配置中指定了使用1号配置类时,加载 MyOtherConfiguration1.java,否则默认加载 MyOtherConfiguration2.java

针对这个场景,我们可以通过以下配置方式进行实现:

import curses 安装 import the configuration_spring_10

import curses 安装 import the configuration_import curses 安装_11

 

至此,我们算是搞清楚了 @Import 注解的工作模式,关于它的工作机制与原理,如果大家感兴趣,可以对源码进行窥探,相关逻辑入口在 org.springframework.context.annotation.ConfigurationClassParser:302行

import curses 安装 import the configuration_spring_12

 

@ImportAutoConfiguration

接下来轮到 @ImportAutoConfiguration 注解了

import curses 安装 import the configuration_加载_04

定睛一看,@ImportAutoConfiguration 继承了 @Import 注解,并导入了一个 ImportSelector 的实现类 ImportAutoConfigurationImportSelector。

根据我们刚才的了解

Import注解 根据导入的ImportSelector实现类所返回的值作为需要导入的 配置类集合,并对这些配置类进行加载。

我们可以猜到 ImportAutoConfigurationImportSelector 肯定是根据某种逻辑返回了需要被导入的配置类集合。

import curses 安装 import the configuration_spring_14

我们发现 ImportAutoConfigurationImportSelector 并没有实现 selectImports 接口,这个接口是由它的父类 AutoConfigurationImportSelector 进行实现的。

import curses 安装 import the configuration_spring_15

可以看到它是调用 getAutoConfigurationEntry 获取配置类集合,继续看 getAutoConfigurationEntry 的逻辑

import curses 安装 import the configuration_spring_16

可以看到它调用了 getCandidateConfigurations 方法获取的配置类集合,就是这个方法被 ImportAutoConfigurationImportSelector 重写了逻辑,如下:

import curses 安装 import the configuration_spring_17

简单解释一下它的逻辑

  1. 先获取当前配置类上的 ImportAutoConfiguration 注解信息
  2. 如果 ImportAutoConfiguration 注解中指定了classes(或value) 属性值,那么直接返回 classes 的值
  3. 如果未配置 classes(或value) 属性值,从 spring.factories 文件中读取key为当前配置类名的结果

 

配合一个案例场景可能更加好理解

在 MyAutoConfiguration 配置类中 导入 MyOtherAutoConfiguration

通过 @ImportAutoConfiguration 注解可以有两种方式实现这个需求

方式一:直接在注解中指定

import curses 安装 import the configuration_import curses 安装_18

方式二:注解中不指定classes或value,并在 spring.factories 中指定目标配置类

import curses 安装 import the configuration_spring_19

import curses 安装 import the configuration_import curses 安装_20

 

总结

相同点

  1. Import 与 ImportAutoConfiguration 注解都可以通过 value 属性指定需要导入的@Configuration配置类
  2. Import 与 ImportAutoConfiguration 注解都支持通过 value 属性配置 ImportSelector类、ImportBeanDefinitionRegistrar类 的方式来间接导入所需的配置类

不同点

  1. @Import 注解只能通过 value 属性值进行导入
  2. @ImportAutoConfiguration 注解的 value 属性值是可选的,当value属性值未指定时,会尝试从 spring.factories 文件中读取相应的配置作为需要导入的配置类集合

 

彩蛋

你知道 @ImportAutoConfiguration 和 @EnableAutoConfiguration 有什么区别吗?

import curses 安装 import the configuration_import curses 安装_21

可以看到很熟悉的一幕,@EnableAutoConfiguration注解继承了 @Import 注解并加载了一个

AutoConfigurationImportSelector,这不就是刚刚 ImportAutoConfigurationImportSelector 的父类吗?

我们看下没有被 ImportAutoConfigurationImportSelector 重写过的逻辑是怎么样的

import curses 安装 import the configuration_加载_22

它直接去 spring.factories 配置文件中寻找 EnableAutoConfiguration 配置项来作为配置类集合进行加载,如下:

import curses 安装 import the configuration_属性值_23

 

那 @EnableAutoConfiguration 在哪里被使用的呢?

import curses 安装 import the configuration_加载_24

@SpringBootApplication 注解继承了 EnableAutoConfiguration 注解。。。

 

那么。。好像发现了 SpringBoot 自动装配的秘密呢 ~~