A
Condition
that offers more fine-grained control when used with@Configuration
. Allows certain conditions to adapt when they match based on the configuration phase. For example, a condition that checks if a bean has already been registered might choose to only be evaluated during theREGISTER_BEAN ConfigurationCondition.ConfigurationPhase
.
先看官方介绍,翻译过来,ConfigurationCondition
接口跟 @Configuration
搭配使用时,能够提供更精细的控制条件。它可以根据配置阶段的匹配情况进行适应或调整 。例如,检查bean是否已经注册的条件可能选择仅在ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN
期间评估。
看完官方介绍反正挺抽象的,咱还是看下 ConfigurationCondition
接口源码,好好理解一下吧。
public interface ConfigurationCondition extends Condition {
/**
* Return the {@link ConfigurationPhase} in which the condition should be evaluated.
*/
ConfigurationPhase getConfigurationPhase();
/**
* The various configuration phases where the condition could be evaluated.
*/
enum ConfigurationPhase {
/**
* The {@link Condition} should be evaluated as a {@code @Configuration}
* class is being parsed.
* <p>If the condition does not match at this point, the {@code @Configuration}
* class will not be added.
*/
PARSE_CONFIGURATION,
/**
* The {@link Condition} should be evaluated when adding a regular
* (non {@code @Configuration}) bean. The condition will not prevent
* {@code @Configuration} classes from being added.
* <p>At the time that the condition is evaluated, all {@code @Configuration}
* classes will have been parsed.
*/
REGISTER_BEAN
}
}
通过源码可以看到评估条件是否生效的分为两个阶段:PARSE_CONFIGURATION
解析配置阶段和 REGISTER_BEAN
注册Bean阶段。那这两个阶段是做什么用的呢?
PARSE_CONFIGURATION
解析配置类阶段:在配置类解析阶段判断配置类是否满足条件,如果配置类上的条件注解不满足条件,配置类将不会被解析,也就是说后续不会被注入到容器中。在该阶段,只能访问@Configuration
类中的静态信息,不能访问Bean中定义的运行时信息。REGISTER_BEAN
注册Bean阶段:这个阶段主要用于将解析得到的配置类和需要注册的Bean注入到容器中。在这个阶段,Spring会根据BeanDefinition
创建相应的Bean实例,并将其注册到容器中。这个阶段也包括对普通bean的注册阶段,将解析得到的配置类和需要注册的Bean注入到容器中。说明: 虽然上面说 PARSE_CONFIGURATION
阶段主要用于解析配置类(@Configuration
),但这个配置类不仅仅指被@Configuration
注解标记的类,还有几个配置类备胎,也就是说被这几个类标记的类也会在配置解析阶段被解析。
具体看 org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate()
方法和 candidateIndicators
属性:
配置类候选类:
private static final Set<String> candidateIndicators = Set.of(
Component.class.getName(),
ComponentScan.class.getName(),
Import.class.getName(),
ImportResource.class.getName());
是否是配置类:
static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
}
// Any of the typical annotations found?
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
return hasBeanMethods(metadata);
}
总结一下配置解析注解包括:@Configuration
、@Component
、@ComponentScan
、@Import
、@Component
、@ImportResource
、@Bean
。在PARSE_CONFIGURATION
阶段,@Bean
注解的信息不会被解析。
本节将介绍 ConfigurationCondition 接口的使用,并通过代码对比 ConfigurationPhase 枚举类两个阶段的不同。
上篇文章说到,某练习两年半的练习生技能包里有唱跳、Rap、打篮球三项技能,我们在配置文件中使用 brother-rooster.skill=sing
属性,配置哪个技能包就打印哪个技能包。现在我们有这么一个需求,如果容器中存在鸡哥(BrotherRooster) 就打印正在使用的技能包,不存在就不使用。
创建一个鸡哥(BrotherRooster) 类:
/**
* @author 公众号-索码理(suncodernote)
*/
public class BrotherRooster {
private String name;
//省去get、set方法
@Override
public String toString() {
return "BrotherRooster{" +
"name='" + name + '\'' +
'}';
}
}
再创建一个配置类注入鸡哥:
/**
* @author 公众号-索码理(suncodernote)
*/
@Configuration
public class BrotherRoosterConfig {
@Bean
BrotherRooster brotherRooster(){
return new BrotherRooster();
}
}
创建一个 ConfigurationCondition
接口实现类 OnBrotherRoosterCondition
,指定 PARSE_CONFIGURATION
解析条件:
public class OnBrotherRoosterCondition implements ConfigurationCondition {
//执行解析阶段
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.PARSE_CONFIGURATION;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 获取所有BrotherRooster类型的Bean
Map<String, BrotherRooster> beansOfType = beanFactory.getBeansOfType(BrotherRooster.class);
// 打印Bean
System.out.println("BrotherRooster Bean:"+beansOfType);
boolean match = !beansOfType.isEmpty();
return match;
}
}
使用 OnBrotherRoosterCondition
类:
/**
* author: 公众号-索码理(suncodernote)
*/
@Conditional(value = {OnBrotherRoosterCondition.class})
@Configuration
public class ConditionConfig {
@Bean("brotherRoosterSkill")
@ConditionalOnSkill("basketball")
BrotherRoosterSkill brotherRoosterSkillBasketball(){
System.out.println("打篮球技能激活。。。。。");
return new BrotherRoosterSkillBasketball();
}
@Bean("brotherRoosterSkill")
@ConditionalOnSkill("rap")
BrotherRoosterSkill brotherRoosterSkillRap(){
System.out.println("Rap技能激活。。。。。");
return new BrotherRoosterRap();
}
@Bean("brotherRoosterSkill")
@ConditionalOnSkill("sing")
BrotherRoosterSkill brotherRoosterSkillSing(){
System.out.println("唱跳技能激活。。。。。");
return new BrotherRoosterSkillSing();
}
}
最后启动项目进行测试,由于上面使用的是 OnBrotherRoosterCondition#getConfigurationPhase()
返回的是ConfigurationPhase.PARSE_CONFIGURATION
配置类解析阶段,此时 BrotherRooster
实例还没有注入到容器中,所以 控制台没有打印任何技能包并且打印的 BrotherRooster Bean
也是空的。
然后我们让 OnBrotherRoosterCondition#getConfigurationPhase()
返回ConfigurationPhase.REGISTER_BEAN
, 控制台打印结果如下:
REGISTER_BEAN打印结果
由于ConfigurationPhase.REGISTER_BEAN
Bean注册阶段,BrotherRooster
实例已经生成且被注入到Bean容器中,所以控制台打印了 唱跳技能激活。。。。。
上面介绍ConfigurationCondition 接口时,我们看到它继承了Condition 接口,那它们之间有什么不同呢?
Condition
先看一下Condition 接口的官方注释,大致意思就是Condition 条件类会在组件注入之前进行检查,然后根据条件决定组件是否要被注入。 Condition 接口和BeanFactoryPostProcessor 接口一样遵循相同的限制,即不要和Bean实例进行交互。如果想和配置类实例进行交互,可以考虑实现ConfigurationCondition 接口。
上面说 Condition 接口和BeanFactoryPostProcessor 接口一样遵循相同的限制,即不要和Bean实例进行交互,那BeanFactoryPostProcessor 接口是什么?它为什么会有这个限制呢?
BeanFactoryPostProcessor的主要作用是在Spring容器加载Bean定义后,在实例化Bean之前对Bean的定义进行修改或扩展。它可以用来动态地修改Bean的定义信息,比如修改属性的值、更改依赖关系等。
为什么在BeanFactoryPostProcessor
阶段不要与Bean实例进行交互呢?主要是在BeanFactoryPostProcessor
阶段,Bean还没有被实例化。如果你试图获取或操作Bean的实例,很可能会遇到空指针异常(NullPointerException) 。
对于 Condition 接口不要与Bean实例这点来说,只是建议不要这么做,因为有时真的会获取不到Bean实例,尤其是在使用 @Bean 注解注入一个实例时, Condition 接口是获取不到的。
综上, Condition 接口 与 ConfigurationCondition 接口有两点不同:
ConfigurationCondition 接口相当于加强了Condition 接口的使用范围,Condition 接口做不到的ConfigurationCondition 接口能够做到;Condition 接口做到的,ConfigurationCondition 接口也能做到。这就是ConfigurationCondition !
绝大多数情况下,使用Condition 接口就够用了,需要获取Bean实例的时候记得使用ConfigurationCondition 接口。