自定义条件利器ConfigurationCondition

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

ConfigurationCondition 介绍

先看官方介绍

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 the REGISTER_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 接口的使用

本节将介绍 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 区别

上面介绍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 接口有两点不同:

  1. ConfigurationCondition 接口对条件的控制更加精细,可以选择在配置阶段解析,也可以在注册Bean阶段解析;Condition 接口在这两个阶段都有解析,无法指定具体的解析阶段。

  2. Condition 接口的实现类中最好不要与Bean实例进行交互,如果需要最好是实现ConfigurationCondition 接口,指定解析阶段为REGISTER_BEAN阶段。

ConfigurationCondition 接口相当于加强了Condition 接口的使用范围,Condition 接口做不到的ConfigurationCondition 接口能够做到;Condition 接口做到的,ConfigurationCondition 接口也能做到。

绝大多数情况下,使用Condition 接口就够用了,需要获取Bean实例的时候记得使用ConfigurationCondition 接口。

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

索码理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值