@Import
和 @Conditional
在 Spring Boot 实现 auto-configuratin
过程中有着至关重要的作用。今天就来梳理一下这两个基础注解。
@Import
注解用来导入更多的类,这个注解等价于XML配置中的 <import/>
。典型的就是导入一个 @Configuration
标记的配置类。
注解参数可以是:
-
@Configuration
注解标记的类,@Configuration
注解本质上是个@Component
,源码的处理上也是判断是否注解了@Component
,因此标记了@Componnet
也是可以的。 -
ImportSelector
接口的子类,此接口重点实现selectImports
方法 返回需要倒入类的名称。 -
ImportBeanDefinitionRegistrar
接口的子类,此接口重点实现registerBeanDefinitions
方法 通过方法参数BeanDefinitionRegistry registry
直接注册bean定义。
以ImportSelector
为例写个测试Demo,源码分析一下:
public static void main(String[] args) {
//这里需要 Spring 托管 @Import 注解的类
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(WithImportAnnotationClass.class);
System.out.println(applicationContext.getBean(BeanA.class));
}
@Import({CustomBeanImportSelector.class})
static class WithImportAnnotationClass {
}
static class CustomBeanImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{BeanA.class.getName()};
}
}
static class BeanA {
}
简单几行代码,启动调试,直接公布调用过程答案:
- Spring Ioc 加载
@Import
注解的类定义,Spring Boot也是类似逻辑,通过 -
ApplicationContext
在创建过程中,通过refresh()
方法 完成 Ioc 容器的初始化工作。这个方法基本包含了 Ioc 的生命周期和重要扩张点。 - 在
refresh
方法中,invokeBeanFactoryPostProcessors()
方法实现中会先后调用BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
和BeanFactoryPostProcesser#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
两个扩展点方法。本质上BeanDefinitionRegistryPostProcessor
是BeanFactoryPostProcesser
的子类,但他们的侧重点不同,前者侧重BeanDefinition
的注册,后者侧重对BeanFactory
处理。 - 针对
AnnotationConfigApplicationContext
它在启动时默认就会注册了一个BeanDefinitionRegistryPostProcessor
,具体对应实现类ConfigurationClassPostProcessor
- 通过
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
方法,1. 明确需要额外处理的BeanDefinition
,2. 解释这些BeanDefinition
产生额外配置的BeanDefinition
。3. 将这些额外配置的BeanDefinition
注册到BeanDefinitionRegistry
中,后期产生实例。
第五步中,有三点强调:
- 明确需要额外处理的
BeanDefinition
,是通过ConfigurationClassUtils#isConfigurationCandidate
方法,判断是否被[@Component, @ComponentScan, @Import, @ImportResource]
标记,然后给BeanDefinition
增加attribution
来实现的(后续会判断这点)。 - 解释这些
BeanDefinition
产生额外配置的BeanDefinition
,是通过ConfigurationClassParser#parser
方法,它根据不通注解和注解配置,来实现产生需要被倒入的BeanDefinition
。当然这个过程涉及到递归,比如说我 Import了一个新的 Import注解的类。 -
ConfigurationClassParser#parser
会对@Conditional
注解进行处理。它在处理开始时,会通过ConditionEvaluator
来判断配置类上是否有Conditional
注解,并根据注解的参数,来判断是否跳过这个配置类。
以上讲完了@Import
注解,有了上面的基础,接下来直接搞 @Conditional
。
- 首先,使用
@Conditional
注解需要指明一个实现了Condition
接口的类,如@Conditional(OnBeanCondition.class)
这样子。 - ApplicationContext 在初始化时,会创建一个
ConditionEvaluator
,它负责对注解的解释工作。 - 在关键点(比如说上面讲的配置类解析过程),
ConditionEvaluator#shouldSkip
返回的Boolean值来跳过某些Bean定义。
第三步中,具体流程其实就是:
获取到Bean定义中的@Conditional
注解,得到配置的Conditon
的实现类,执行实现类的matches
方法。
像Spring Boot中有一些自带的 Condition
的实现类如:
OnBeanCondition => @ConditionalOnBean
OnClassCondition => @ConditionalOnClass