@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;
    }

如果按照含义,那我们的预期就是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 })

那么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);

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



public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

注意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());
	}

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值