与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
这个需要从@Conditional注解的工作原理来看
@Conditional(XXX.class)
判断bean相关的是
@Conditional(OnBeanCondition.class)
首先,@Conditional注解判断是否满足条件是在扫描bean定义的阶段
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是无法起到判断作用的。