@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是无法起到判断作用的。

### 回答1: @ConditionalOnBean 是 Spring 框架中的一个条件注解,用于指定在特定的 Bean 存在时才会生效。 通常情况下,我们使用 @ConditionalOnBean 注解来限制某个 Bean 的加载条件。当被注解的类或方法放置在 Bean 的定义上时,Spring 容器会根据指定的条件判断是否加载该 Bean。只有当条件满足时,才会将该 Bean 加入到 Spring 容器中。 @ConditionalOnBean 注解可以用于任何 Spring Bean 上,如 @Service、@Component、@Configuration 等等。它的参数是一个或多个 Class 类型的数组,指定了需要判断是否存在的 Bean 的类型。只有当指定的所有 Bean 都存在于 Spring 容器中,条件才会满足。 使用 @ConditionalOnBean 注解时,可以将多个条件连接起来以构成复杂的条件判断。通过在多个条件注解之间添加逻辑运算符,如 @ConditionalOnBean({BeanA.class, BeanB.class}) 表示只有当 BeanA 和 BeanB 同时存在时,条件才会满足。 这个注解的主要作用是根据特定的 Bean 的存在与否来决定特定的功能是否可用。比如,当我们的应用依赖于某个外部库的存在时,可以使用 @ConditionalOnBean 注解来限制某个组件的加载,在外部库存在时才加载该组件。 总之,@ConditionalOnBean 是 Spring 框架中用于条件化加载 Bean 的注解之一,可以根据特定 Bean 的存在来决定是否加载某个 Bean,从而实现更加灵活和可配置的应用开发。 ### 回答2: @ConditionalOnBean是Spring框架中的一个注解,用于指定一个组件的条件,只有当指定的Bean存在于Spring容器中时,才会创建被注解的组件。 使用@ConditionalOnBean注解可以在某些特定的条件下,动态地决定是否创建一个特定的组件。它的作用是根据指定的Bean是否存在来决定某个组件是否应该被创建。 @ConditionalOnBean的使用方法是将它放在一个类上面,这个类可以是任意可以被Spring容器扫描到的类,比如配置类、bean类等。注解中需要指定的参数是一个或多个被依赖的Bean的类名或类的全限定名。 当被注解的类被加载到Spring容器中时,会先检查指定的Bean是否存在于容器中。如果存在,则创建被注解的组件;如果不存在,则不创建该组件。 通过@ConditionalOnBean注解,我们可以根据应用条件来动态地控制组件的创建。比如,我们可以根据不同的数据源配置,优先选择某个特定的数据源作为默认数据源。 总结起来,@ConditionalOnBean是一个根据指定的Bean的存在与否来决定是否创建某个组件的注解。它提供了一种简单而灵活的方式来根据特定条件动态地选择组件的创建。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值