前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自定义条件利器ConfigurationCondition

自定义条件利器ConfigurationCondition

作者头像
索码理
发布2024-02-01 16:08:12
810
发布2024-02-01 16:08:12
举报
文章被收录于专栏:索码理索码理

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 接口源码,好好理解一下吧。

代码语言:javascript
复制
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 属性:

配置类候选类:

代码语言:javascript
复制
private static final Set<String> candidateIndicators = Set.of(
   Component.class.getName(),
   ComponentScan.class.getName(),
   Import.class.getName(),
   ImportResource.class.getName());

是否是配置类:

代码语言:javascript
复制
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) 类:

代码语言:javascript
复制
/**
 * @author 公众号-索码理(suncodernote)
 */
public class BrotherRooster {

    private String name;
 
 //省去get、set方法

    @Override
    public String toString() {
        return "BrotherRooster{" +
                "name='" + name + '\'' +
                '}';
    }
}

再创建一个配置类注入鸡哥:

代码语言:javascript
复制
/**
 * @author 公众号-索码理(suncodernote)
 */
@Configuration
public class BrotherRoosterConfig {

    @Bean
    BrotherRooster brotherRooster(){
        return  new BrotherRooster();
    }
}

创建一个 ConfigurationCondition 接口实现类 OnBrotherRoosterCondition ,指定 PARSE_CONFIGURATION 解析条件:

代码语言:javascript
复制
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类:

代码语言:javascript
复制
/**
 * 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 接口也能做到。这就是ConfigurationCondition

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 索码理 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ConfigurationCondition 介绍
    • 先看官方介绍
      • 再看源码
      • ConfigurationCondition 接口的使用
      • ConfigurationCondition 与 Condition 区别
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档