SpringBoot用配置影响Bean加载@ConditionalOnProperty

目录
  • 故事背景
  • 调试&解决
  • SpringBoot 是怎么做的
  • 故事的最后

故事背景

故事发生在几个星期前,自动化平台代码开放给整个测试团队以后,越来越多的同事开始探索平台代码。为了保障自动化测试相关的数据和沉淀能不被污染,把数据库进行了隔离。终于有了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的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring populateBean属性赋值和自动注入

    目录 正文 一.postProcessAfterInstantiation:修改Bean实例 二.autowireByName:根据名称自动注入 三.autowireByType:根据类型自动注入 四.postProcessPropertyValues:处理属性值(@Resource.@Autowired.@Value) 五.applyPropertyValues:填充属性 5.1 解析依赖 5.2 解析List 正文 protected void populateBean(String bea

  • SpringIOC容器Bean的作用域及生命周期实例

    目录 bean作用域 1. 默认单实例 2. 设置多实例 bean生命周期 一.生命周期过程示例 二.更完整的过程 1. 创建后置处理器 bean作用域 bean的作用域,其实就是设置创建 bean 的实例是属于单实例,还是多实例. 1. 默认单实例 默认情况下,创建的 bean 是单实例对象. 比如,用之前的代码为例: @Test public void testCollection2() { ApplicationContext context = new ClassPathXmlAppli

  • spring 自动注入AutowiredAnnotationBeanPostProcessor源码解析

    目录 一.MergedBeanDefinitionPostProcessor 1.1.postProcessMergedBeanDefinition 1.1.1 findAutowiringMetadata 查询属性或方法上有@Value和@Autowired注解的元素 1.1.2 检查元数据信息 二.SmartInstantiationAwareBeanPostProcessor 2.1.determineCandidateConstructors 一.MergedBeanDefinition

  • Spring容器刷新obtainFreshBeanFactory示例详解

    目录 Spring容器刷新—02—obtainFreshBeanFactory BeanFactory和ApplicationContext obtainFreshBeanFactory 1.GenericApplicationContext系列的实现 2.AbstractRefreshableApplicationContext系列的实现 该使用哪个BeanFactory? Servlet环境 SpringBoot环境 Spring容器刷新—02—obtainFreshBeanFactory

  • 全面详解Spring Bean生命周期教程示例

    目录 Spring 中 Bean 的生命周期 Bean 的实例化 构造方法注入 工厂方法注入 Bean 的属性赋值 setter注入 构造方法注入 Bean 的初始化 初始化方法 InitializingBean 接口 Bean 的销毁 销毁方法 DisposableBean 接口 总结 Spring 中 Bean 的生命周期 是当今最流行的 Java 开发框架之一,其强大的 Bean容器机制是其中的核心之一.Bean 是指在 Spring 容器中被管理的对象,它们可以被注入到其他对象中,也可以

  • Spring createBeanInstance实例化Bean

    目录 Spring实例Bean的方法 一.determineConstructorsFromBeanPostProcessors:确定构造参数 二.autowireConstructor:有参构造创建Bean 2.1 resolveConstructorArguments:解析参数并返回最小参数个数 2.2 createArgumentArray:创建构造参数数组 2.3 instantiate:实例化Bean 三.instantiateBean:无参构造创建Bean 无参构造 四.使用实例化策

  • SpringBoot用配置影响Bean加载@ConditionalOnProperty

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

  • 浅谈SpringBoot Bean加载优先级的问题

    目录 Bean加载优先级的问题 同一个类中加载顺序 @DependsOn控制顺序 @Order不能控制顺序 Spring控制Bean加载顺序 使用Spring @Order控制bean加载顺序 使用Spring @DependsOn控制bean加载顺序 小结一下 Bean加载优先级的问题 spring容器载入bean顺序是不确定的,spring框架没有约定特定顺序逻辑规范.但spring保证如果A依赖B(如beanA中有@Autowired B的变量),那么B将先于A被加载. 同一个类中加载顺序

  • Springboot常用注解及配置文件加载顺序详解

    Springboot常用注解及底层实现 1.@SpringBootApplication:这个注解标识了一个SpringBoot工程,她实际上是另外三个注解的组合,分别是: @SpringBootConfiguration:源码可以看到,这个注解除了元注解外,实际就只有一个@Configuration,把该类变成一个配置类,表示启动类也是一个配置类: @EnableAutoConfiguration:是开启自动配置的功能,向Spring容器中导入了一个Selector,用来加载ClassPath

  • SpringBoot使用Shiro实现动态加载权限详解流程

    目录 一.序章 二.SpringBoot集成Shiro 1.引入相关maven依赖 2.自定义Realm 3.Shiro配置类 三.shiro动态加载权限处理方法 四.shiro中自定义角色与权限过滤器 1.自定义uri权限过滤器 zqPerms 2.自定义角色权限过滤器 zqRoles 3.自定义token过滤器 五.项目中会用到的一些工具类常量等 1.Shiro工具类 2.Redis常量类 3.Spring上下文工具类 六.案例demo源码 一.序章 基本环境 spring-boot 2.1

  • springboot启动时是如何加载配置文件application.yml文件

    今天启动springboot时,明明在resources目录下面配置了application.yml的文件,但是却读不出来,无奈看了下源码,总结一下springboot查找配置文件路径的过程,能力有限,欢迎各位大牛指导!!! spring加载配置文件是通过listener监视器实现的,在springboot启动时: 在容器启动完成后会广播一个SpringApplicationEvent事件,而SpringApplicationEvent事件是继承自ApplicationEvent时间的,代码如下

  • 详解springboot启动时是如何加载配置文件application.yml文件

    今天启动springboot时,明明在resources目录下面配置了application.yml的文件,但是却读不出来,无奈看了下源码,总结一下springboot查找配置文件路径的过程,能力有限,欢迎各位大牛指导!!! spring加载配置文件是通过listener监视器实现的,在springboot启动时: 在容器启动完成后会广播一个SpringApplicationEvent事件,而SpringApplicationEvent事件是继承自ApplicationEvent时间的,代码如下

  • springboot默认的5种加载路径详解

    目录 前言 一.application.properties/.yml文件初识 二.application.properties/.yml文件可以在其他路径吗 三.总结 前言 上次分享了如何一步一步搭建一个springboot的项目,详细参见<5分钟快速搭建一个springboot的项目>,最终的结果是在”8080“端口搭建起了服务,并成功访问.不知道有小伙伴是否有疑惑,springboot应该有配置文件的,一般的配置文件都是application.properties或者applicatio

  • thinkPHP5.0框架配置格式、加载解析与读取方法

    本文实例讲述了thinkPHP5.0框架配置格式.加载解析与读取方法.分享给大家供大家参考,具体如下: ThinkPHP支持多种格式的配置格式,但最终都是解析为PHP数组的方式. PHP数组定义 返回PHP数组的方式是默认的配置定义格式,例如: //项目配置文件 return [ // 默认模块名 'default_module' => 'index', // 默认控制器名 'default_controller' => 'Index', // 默认操作名 'default_action' =

  • Spring bean 加载执行顺序实例解析

    本文研究的主要是Spring bean 加载执行顺序的相关内容,具体如下. 问题来源: 有一个bean为A,一个bean为B.想要A在容器实例化的时候的一个属性name赋值为B的一个方法funB的返回值. 如果只是在A里单纯的写着: private B b; private String name = b.funb(); 会报错说nullpointException,因为这个时候b还没被set进来,所以为null. 解决办法为如下代码,同时学习下spring中 InitializingBean

  • 使用springboot单元测试对weblistener的加载测试

    目录 springboot单元测试对weblistener的加载测试 原监听器代码 测试类 springboot web做单元测试 springboot单元测试对weblistener的加载测试 使用spring-boot对web项目进行测试时对weblistener进行加载.以proxool连接池的加载为例. 原监听器代码 @WebListener public class ProxoolListener implements ServletContextListener{ @Override

随机推荐