SpringBoot用配置影响Bean加载@ConditionalOnProperty

 更新时间:2023年04月06日 10:06:55   作者:树叶小记  
这篇文章主要为大家介绍了SpringBoot用配置影响Bean加载@ConditionalOnProperty示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

故事背景

故事发生在几个星期前,自动化平台代码开放给整个测试团队以后,越来越多的同事开始探索平台代码。为了保障自动化测试相关的数据和沉淀能不被污染,把数据库进行了隔离。终于有了2个数据库实例,一个给dev环境用,一个给test环境用。可是随着平台的发展,越来越多的中间件被引用了。所以需要隔离的东西就越来越多了,比如MQ,Redis等。成本越来越高,如果像数据库实例一样全部分开搞一套,那在当下全域降本增效的大潮下,也是困难重重。

通过线下观察和走访发现,这些探索的同学并不是需要全平台的能力,其中有很多模块或者子系统,同学并不关心。因此就产生了一个想法,隔离掉这些类或者不使用这些和中间件相关的类应该就可以了 。而后因为我们的平台是基于springboot开发的,自然而然的想到了@Conditional注解。

调试&解决

以AWS SQS为例,先添加上了注解@ConditionalOnProperty根据配置信息中的coverage.aws.topic属性进行判断,如果存在这个配置就进行CoverageSQSConfig的Spring Bean的加载。

@Configuration
@ConditionalOnProperty(
        name = "coverage.aws.topic"
)
public class CoverageSQSConfig {
    @Value("${coverage.aws.region}")
    private String awsRegion;
    @Value("${coverage.aws.access.key}")
    private String accessKey;
    @Value("${coverage.aws.secret.key}")
    private String secretKey;
    @Bean(name = "coverageSQSListenerFactory")
    public DefaultJmsListenerContainerFactory sqsListenerContainerFactory(){
        return getDefaultJmsListenerContainerFactory(awsRegion, accessKey, secretKey);
    }
    private DefaultJmsListenerContainerFactory getDefaultJmsListenerContainerFactory(String awsRegion, String accessKey, String secretKey) {
        DefaultJmsListenerContainerFactory sqsFactory = new DefaultJmsListenerContainerFactory();
        sqsFactory.setConnectionFactory(new SQSConnectionFactory(
                new ProviderConfiguration(),
                AmazonSQSClientBuilder.standard()
                        .withRegion(Region.of(awsRegion).id())
                        .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
                        .build()));
        sqsFactory.setConcurrency("3-10");
        sqsFactory.setReceiveTimeout(10*1000L);
        sqsFactory.setRecoveryInterval(1000L);
        return sqsFactory;
    }
}

为调试这个内容的效果,这里列出了2次调试的效果对比:首先是把备注字段全部都注释掉。

通过上图很明显,当coverage.aws.topic属性不存在的时候,不能找到被Spring统一管理的bean。

第二次是把备注的注释都取消掉,重启后能找到bean。

问题解决了吗?当时就想再看下SpringBoot是怎么做的通过这个注解就这么方便的过滤了这个bean的加载,以及是否有什么其他的用法或者特性。

SpringBoot 是怎么做的

通过@ConditionalOnProperty注解,很快能定位到它是位于 autoconfigure模块的特性。**
**

顺藤摸瓜,很快就能找到注解是在哪里进行使用的

package org.springframework.boot.autoconfigure.condition;
...
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
  @Override
  public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // 通过获类原始数据上的ConditionalOnProperty注解的参数值
    List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
        metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
    List<ConditionMessage> noMatch = new ArrayList<>();
    List<ConditionMessage> match = new ArrayList<>();
    for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
     // 通过属性值,逐一判断配置信息中的信息是否满足 , context.getEnvironment() 能获取到所有的配置信息
      ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
      (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
    }
    if (!noMatch.isEmpty()) {
      return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
    }
    return ConditionOutcome.match(ConditionMessage.of(match));
  }
  private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
      MultiValueMap<String, Object> multiValueMap) {
    ...
    return annotationAttributes;
  }
  private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
    Spec spec = new Spec(annotationAttributes);
    List<String> missingProperties = new ArrayList<>();
    List<String> nonMatchingProperties = new ArrayList<>();
    // 通过属性值,判断配置信息中的信息是否满足
    spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
    if (!missingProperties.isEmpty()) {
      return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
          .didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
    }
    if (!nonMatchingProperties.isEmpty()) {
      return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
          .found("different value in property", "different value in properties")
          .items(Style.QUOTE, nonMatchingProperties));
    }
    return ConditionOutcome
        .match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
  }
  private static class Spec {
    private final String prefix;
    private final String havingValue;
    private final String[] names;
    private final boolean matchIfMissing;
    Spec(AnnotationAttributes annotationAttributes) {
      ...
    }
    private String[] getNames(Map<String, Object> annotationAttributes) {
      ...
    }
    private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
      for (String name : this.names) {
        String key = this.prefix + name;
        if (resolver.containsProperty(key)) {
        // havingValue 默认为 "" 
          if (!isMatch(resolver.getProperty(key), this.havingValue)) {
            nonMatching.add(name);
          }
        }
        else {
          if (!this.matchIfMissing) {
            missing.add(name);
          }
        }
      }
    }
    private boolean isMatch(String value, String requiredValue) {
      if (StringUtils.hasLength(requiredValue)) {
        return requiredValue.equalsIgnoreCase(value);
      }
      // havingValue 默认为 "" ,因此只要对应的属性不为false,在注解中没填havingValue的情况下,都是会match上conditon,即都会被加载
      return !"false".equalsIgnoreCase(value);
    }
    @Override
    public String toString() {
      ...
    }
  }
}

用这种方式进行SpingBoot扩展的也特别多,SpingBoot自己的autoconfigure模块中有很多模块的增强用的也是这个注解。

那他是在哪个环节进行的这个condition的判断呢?简单标注如下:

其中判断过滤的总入口:

// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
  /**
   * Determine whether the given class does not match any exclude filter
   * and does match at least one include filter.
   * @param metadataReader the ASM ClassReader for the class
   * @return whether the class qualifies as a candidate component
   */
  protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        return false;
      }
    }
    for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        // conditons 相关的入口,
        return isConditionMatch(metadataReader);
      }
    }
    return false;
  }

环顾整个流程,这里比较好的一点就是一旦条件过滤后,那就对类元文件里面的其他内容也不进行加载,像下面的@Value和@Bean的填充也不会进行,能优雅高效的解决掉当前的问题。

    @Value("${coverage.aws.region}")
    private String awsRegion;
    @Value("${coverage.aws.access.key}")
    private String accessKey;
    @Value("${coverage.aws.secret.key}")
    private String secretKey;
    @Bean(name = "coverageSQSListenerFactory")
    public DefaultJmsListenerContainerFactory sqsListenerContainerFactory(){
        return getDefaultJmsListenerContainerFactory(awsRegion, accessKey, secretKey);
    }

故事的最后

做完这个改动以后,就提交了代码,妈妈再也不用担心因为其他人不小心使用某些只有一个实例的中间件导致数据污染了。用注解方式解决这个通过配置就能控制加载bean的这个能力确实很方便很Boot。比如中间件团队提供组件能力给团队,用condtion的这个特性也是能方便落地的。当然condition里面还有其他的一些特性,这里只是抛砖引玉,简单的梳理一下最近的一个使用场景。

以上就是SpringBoot用配置影响Bean加载@ConditionalOnProperty的详细内容,更多关于SpringBoot Bean加载@ConditionalOnProperty的资料请关注脚本之家其它相关文章!

相关文章

  • java中计算字符串长度的方法及u4E00与u9FBB的认识

    java中计算字符串长度的方法及u4E00与u9FBB的认识

    字符串采用unicode编码的方式时,计算字符串长度的方法找出UNICODE编码中的汉字的代表的范围“\u4E00” 到“\u9FBB”之间感兴趣的朋友可以参考本文,或许对你有所帮助
    2013-01-01
  • java内存溢出示例(堆溢出、栈溢出)

    java内存溢出示例(堆溢出、栈溢出)

    这篇文章主要介绍了java内存溢出示例(堆溢出、栈溢出),需要的朋友可以参考下
    2014-04-04
  • Java匿名类和匿名函数的概念和写法

    Java匿名类和匿名函数的概念和写法

    匿名函数写法和匿名类写法的前提必须基于函数式接口匿名函数写法和匿名类写法其本质是同一个东西,只是简化写法不同使用Lambda表达式简写匿名函数时,可以同时省略实现类名、函数名,这篇文章主要介绍了Java匿名类和匿名函数的概念和写法,需要的朋友可以参考下
    2023-06-06
  • Java Agent (代理)探针技术详情

    Java Agent (代理)探针技术详情

    这篇文章主要介绍了Java Agent 探针技术详情,Java 中的 Agent 技术可以让我们无侵入性的去进行代理,最常用于程序调试、热部署、性能诊断分析等场景,下文更多相关资料,感兴趣的小伙伴可以参考一下
    2022-04-04
  • java代理模式(静态代理、动态代理、cglib代理)

    java代理模式(静态代理、动态代理、cglib代理)

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;这篇文章主要介绍了Java 中的三种代理模式,需要的朋友可以参考下,希望能给你带来帮助
    2021-07-07
  • java建立子类方法总结

    java建立子类方法总结

    在本篇文章里小编给大家分享了关于java建子类的步骤和方法,需要的朋友们跟着学习下。
    2019-05-05
  • Quartz实现JAVA定时任务的动态配置的方法

    Quartz实现JAVA定时任务的动态配置的方法

    这篇文章主要介绍了Quartz实现JAVA定时任务的动态配置的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • java操作gaussDB数据库的实现示例

    java操作gaussDB数据库的实现示例

    本文主要介绍了java操作gaussDB数据库的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Java向上转型和向下转型的区别说明

    Java向上转型和向下转型的区别说明

    这篇文章主要介绍了Java向上转型和向下转型的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • JavaWeb实现简单查询商品功能

    JavaWeb实现简单查询商品功能

    这篇文章主要为大家详细介绍了JavaWeb实现简单查询商品功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07

最新评论