• @ConditionalOnBean系列注解使用误区


    用法简介

    与bean相关的几个condition注解有
    @ConditionalOnBean当 某个Bean存在成立
    @ConditionalOnSingleCandidate表示当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean
    @ConditionalOnMissingBean当某个bean不存在时成立

    试验

    如果按照几个注解的字面意思去使用,会发现和我们的预期完全不同。
    例如,在某个配置类声明了两个Bean

        @Bean
        @ConditionalOnMissingBean(StringRedisTemplate.class)
        public StringRedisTemplate stringRedisTemplateOne(
                RedisConnectionFactory redisConnectionFactory) {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
        @Bean
        public StringRedisTemplate stringRedisTemplateTwo(
                RedisConnectionFactory redisConnectionFactory) {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如果按照含义,那我们的预期就是stringRedisTemplateOne方法不会执行,也不会创建stringRedisTemplateOne这个bean。但是我们实际执行发现两个方法都被执行,分别创建了stringRedisTemplateOne和stringRedisTemplateTwo两个bean!!!

    结论

    先说结论
    如果想正确使用这几个注解,需要对bean扫描的顺序有一个认识,因为在判断某个bean是否满足条件时,其只会根据工厂中现在已经有的beanDefinition判断,因此一定要谨慎使用
    例如上面的stringRedisTemplateOne比stringRedisTemplateTwo先扫描到,在加载bean定义时先判断的是stringRedisTemplateOne,此时bean工厂中还没有stringRedisTemplateTwo,因此认为匹配成功,满足!!stringRedisTemplateOne被加载
    大致的bean加载顺序为 当前项目直接扫描到的bean > 通过spring.factories引入的EnableAutoConfiguration中指定的bean。
    如果使用Import注解,例如

    @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
    
    • 1

    那么LettuceConnectionConfiguration > JedisConnectionConfiguration

    why

    这个需要从@Conditional注解的工作原理来看
    @Conditional(XXX.class)
    判断bean相关的是
    @Conditional(OnBeanCondition.class)
    在这里插入图片描述

    首先,@Conditional注解判断是否满足条件是在扫描bean定义的阶段

    1. 在扫描bean时根据ComponentScan,import,EnableAutoConfiguration等类扫描,会把扫描到的类挨个判断是否满足条件,不满足条件的直接被跳过,这个阶段起作用的是OnClassCondition,即判断类是否存在的condition
    2. 在所有类扫描完毕之后,会将所有扫描到的类,加载成beanDefinition在这个阶段
    			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
    			parser.parse(candidates);
    			parser.validate();
    
    			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    			configClasses.removeAll(alreadyParsed);
    
    			// Read the model and create bean definitions based on its content
    			if (this.reader == null) {
    				this.reader = new ConfigurationClassBeanDefinitionReader(
    						registry, this.sourceExtractor, this.resourceLoader, this.environment,
    						this.importBeanNameGenerator, parser.getImportRegistry());
    			}
               // 将所有类加载成BeanDefinition
    			this.reader.loadBeanDefinitions(configClasses);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    、org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions

    
    
    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    		for (ConfigurationClass configClass : configurationModel) {
    			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意configurationModel虽然是set但是是LinkedHashSet,也就是有序,可以保持添加顺序,因此是是按照扫描类的顺序进行BeanDefinition加载。
    在加载bean时根据Condition条件判断是否需要跳过。
    org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass

    private void loadBeanDefinitionsForConfigurationClass(
    			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    
    		if (trackedConditionEvaluator.shouldSkip(configClass)) {
    			String beanName = configClass.getBeanName();
    			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
    				this.registry.removeBeanDefinition(beanName);
    			}
    			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
    			return;
    		}
    
    		if (configClass.isImported()) {
    			registerBeanDefinitionForImportedConfigurationClass(configClass);
    		}
    		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    			loadBeanDefinitionsForBeanMethod(beanMethod);
    		}
    
    		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    可以看出来,先判断是否应该跳过该bean,如果满足条件,注册到bean工厂,如果不满足则跳过。OnBeanCondition开始发挥作用
    因为每次判断bean都是从bean工厂中判断的。那么OnBeanCondition只对当前bean之前就已经被加载的bean可以起到判断作用,而对此时还没有注册到bean工厂的,将要注册的bean是无法起到判断作用的。

  • 相关阅读:
    linux常用命令记录
    博客从 CloudBase 迁移至云主机
    使用赋值方法画图形
    【ASP.NET Core 基础知识】--最佳实践和进阶主题--设计模式在ASP.NET Core中的应用
    react写倒计时
    电子技术基础之一(电容和电感)
    姚期智、张亚勤、薛澜、Stuart Russell、Max Tegmark,DeepMind研究员等共话全球AI治理丨大会回顾...
    2023全国大学生软件测试大赛开发者测试练习题99分答案(ScapegoatTree2023)
    Vant轮播多个div结合二维数组的运用
    视频推拉流/直播点播平台EasyDSS分享的链接提示“无信号”,该如何解决?
  • 原文地址:https://blog.csdn.net/qq_37436172/article/details/126442497