条件注解是Spring4提供的一种bean加载特性,主要用于控制配置类和bean初始化条件。在springBoot,springCloud一系列框架底层源码中,条件注解的使用到处可见。
不少人在使用 @ConditionalOnBean 注解时会遇到不生效的情况,依赖的 bean 明明已经配置了,但就是不生效。是不是@ConditionalOnBean和 Bean加载的顺序有没有关系呢?
本篇文章就针对这个问题,跟着源码,一探究竟。
问题演示:
-
@Configuration -
public class Configuration1 { -
@Bean -
@ConditionalOnBean(Bean2.class) -
public Bean1 bean1() { -
return new Bean1(); -
} -
}
-
@Configuration -
public class Configuration2 { -
@Bean -
public Bean2 bean2(){ -
return new Bean2(); -
} -
}
运行结果:@ConditionalOnBean(Bean2.class)返回false。明明定义的有bean2,bean1却未加载。
源码分析
首先要明确一点,条件注解的解析一定发生在spring ioc的bean definition阶段,因为 spring bean初始化的前提条件就是有对应的bean definition,条件注解正是通过判断bean definition来控制bean能否被解析。
对上述示例进行源码调试。
从 bean definition解析的入口开始:ConfigurationClassPostProcessor
-
@Override -
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { -
int registryId = System.identityHashCode(registry); -
if (this.registriesPostProcessed.contains(registryId)) { -
throw new IllegalStateException( -
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); -
} -
if (this.factoriesPostProcessed.contains(registryId)) { -
throw new IllegalStateException( -
"postProcessBeanFactory already called on this post-processor against " + registry); -
} -
this.registriesPostProcessed.add(registryId); -
// 解析bean definition入口 -
processConfigBeanDefinitions(registry); -
}
跟进processConfigBeanDefinitions方法:
-
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { -
//省略不必要的代码... -
//解析候选bean,先获取所有的配置类,也就是@Configuration标注的类 -
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()); -
} -
//开始解析配置类,也就是条件注解解析的入口 -
this.reader.loadBeanDefinitions(configClasses); -
alreadyParsed.addAll(configClasses); -
//... -
}
跟进条件注解解析入口loadBeanDefinitions,开始循环解析所有的配置类。这里是所有自定义的配置类和自动装配的配置类,如下:

上述代码开始解析配置类。如果配置类中有@Bean标注的方法,则会调用loadBeanDefinitionsForBeanMethod()来获得所有方法。然后循环解析,解析时会执行如下校验方法,也正是条件注解的入口:
-
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { -
//判断是否有条件注解,否则直接返回 -
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { -
return false; -
} -
if (phase == null) { -
if (metadata instanceof AnnotationMetadata && -
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { -
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); -
} -
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); -
} -
//获取当前定义bean的方法上,所有的条件注解 -
List<Condition> conditions = new ArrayList<>(); -
for (String[] conditionClasses : getConditionClasses(metadata)) { -
for (String conditionClass : conditionClasses) { -
Condition condition = getCondition(conditionClass, this.context.getClassLoader()); -
conditions.add(condition); -
} -
} -
//根据Order来进行排序 -
AnnotationAwareOrderComparator.sort(conditions); -
//遍历条件注解,开始执行条件注解的流程 -
for (Condition condition : conditions) { -
ConfigurationPhase requiredPhase = null; -
if (condition instanceof ConfigurationCondition) { -
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); -
} -
//这里执行条件注解的 condition.matches 方法来进行匹配,返回布尔值 -
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { -
return true; -
} -
} -
return false; -
}
继续跟进条件注解的匹配方法,开始解析示例代码中bean1的配置:
-
@Bean -
@ConditionalOnBean(Bean2.class) -
public Bean1 bean1() { -
return new Bean1(); -
}

在getMatchOutcome方法中,参数metadata是要解析的目标bean,也就是bean1。条件注解依赖的bean被封装成了BeanSearchSpec,从名字可以看出是要寻找的对象,这是一个静态内部类,构造方法如下:
-
BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, -
Class<?> annotationType) { -
this.annotationType = annotationType; -
//读取 metadata中的设置的value -
MultiValueMap<String, Object> attributes = metadata -
.getAllAnnotationAttributes(annotationType.getName(), true); -
//设置各参数,根据这些参数进行寻找目标类 -
collect(attributes, "name", this.names); -
collect(attributes, "value", this.types); -
collect(attributes, "type", this.types); -
collect(attributes, "annotation", this.annotations); -
collect(attributes, "ignored", this.ignoredTypes); -
collect(attributes, "ignoredType", this.ignoredTypes); -
this.strategy = (SearchStrategy) metadata -
.getAnnotationAttributes(annotationType.getName()).get("search"); -
BeanTypeDeductionException deductionException = null; -
try { -
if (this.types.isEmpty() && this.names.isEmpty()) { -
addDeducedBeanType(context, metadata, this.types); -
} -
} -
catch (BeanTypeDeductionException ex) { -
deductionException = ex; -
} -
validate(deductionException); -
}
继续跟进搜索bean的方法:
MatchResult matchResult = getMatchingBeans(context, spec);
-
private MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { -
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); -
if (beans.getStrategy() == SearchStrategy.ANCESTORS) { -
BeanFactory parent = beanFactory.getParentBeanFactory(); -
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, -
"Unable to use SearchStrategy.PARENTS"); -
beanFactory = (ConfigurableListableBeanFactory) parent; -
} -
MatchResult matchResult = new MatchResult(); -
boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT; -
List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType( -
beans.getIgnoredTypes(), beanFactory, context, considerHierarchy); -
//因为实例代码中设置的是类型,所以这里会遍历类型,根据type获取目标bean是否存在 -
for (String type : beans.getTypes()) { -
Collection<String> typeMatches = getBeanNamesForType(beanFactory, type, -
context.getClassLoader(), considerHierarchy); -
typeMatches.removeAll(beansIgnoredByType); -
if (typeMatches.isEmpty()) { -
matchResult.recordUnmatchedType(type); -
} -
else { -
matchResult.recordMatchedType(type, typeMatches); -
} -
} -
//根据注解寻找 -
for (String annotation : beans.getAnnotations()) { -
List<String> annotationMatches = Arrays -
.asList(getBeanNamesForAnnotation(beanFactory, annotation, -
context.getClassLoader(), considerHierarchy)); -
annotationMatches.removeAll(beansIgnoredByType); -
if (annotationMatches.isEmpty()) { -
matchResult.recordUnmatchedAnnotation(annotation); -
} -
else { -
matchResult.recordMatchedAnnotation(annotation, annotationMatches); -
} -
} -
//根据设置的name进行寻找 -
for (String beanName : beans.getNames()) { -
if (!beansIgnoredByType.contains(beanName) -
&& containsBean(beanFactory, beanName, considerHierarchy)) { -
matchResult.recordMatchedName(beanName); -
} -
else { -
matchResult.recordUnmatchedName(beanName); -
} -
} -
return matchResult; -
}
getBeanNamesForType()方法最终会委托给BeanTypeRegistry类的getNamesForType方法来获取对应的指定类型的bean name:
-
Set<String> getNamesForType(Class<?> type) { -
//同步spring容器中的bean -
updateTypesIfNecessary(); -
//返回指定类型的bean -
return this.beanTypes.entrySet().stream() -
.filter((entry) -> entry.getValue() != null -
&& type.isAssignableFrom(entry.getValue())) -
.map(Map.Entry::getKey) -
.collect(Collectors.toCollection(LinkedHashSet::new)); -
}
重点来了。
上述方法中的第一步便是同步bean,也就是获取此时 spring 容器中的所有 beanDifinition。只有这样,条件注解的判断才有意义。
我们跟进updateTypesIfNecessary():
-
private void updateTypesIfNecessary() { -
//这里lastBeanDefinitionCount 代表已经同步的数量,如果和容器中的数量不相等,才开始同步。 -
//否则,获取beanFactory迭代器,开始同步。 -
if (this.lastBeanDefinitionCount != this.beanFactory.getBeanDefinitionCount()) { -
Iterator<String> names = this.beanFactory.getBeanNamesIterator(); -
while (names.hasNext()) { -
String name = names.next(); -
if (!this.beanTypes.containsKey(name)) { -
addBeanType(name); -
} -
} -
//同步完之后,更新已同步的beanDefinition数量。 -
this.lastBeanDefinitionCount = this.beanFactory.getBeanDefinitionCount(); -
} -
}
离答案只差一步了,就是看一下从beanFactory中迭代的是哪些beanDefinition?
继续跟进getBeanNamesIterator():
-
@Override -
public Iterator<String> getBeanNamesIterator() { -
CompositeIterator<String> iterator = new CompositeIterator<>(); -
iterator.add(this.beanDefinitionNames.iterator()); -
iterator.add(this.manualSingletonNames.iterator()); -
return iterator; -
}
分别来看:
-
beanDefinitionNames就是存储一些自动解析和装配的bean,我们的启动类、配置类、controller、service等。 -
manualSingletonNames,从名字可以看出,手工单例名称。什么意思呢?在spring ioc的过程中,会手动触发一些bean的注册。比如在springboot启动过程中,会显示的注册一些配置 bean,如:springBootBanner,systemEnvironment,systemProperties等。
我们来分析一下上面示例bean1为何没有实例化?
在spring ioc的过程中,优先解析@Component,@Service,@Controller注解的类。其次解析配置类,也就是@Configuration标注的类。最后开始解析配置类中定义的bean。
示例代码中bean1是定义在配置类中的,当执行到配置类解析的时候,@Component,@Service,@Controller ,@Configuration标注的类已经全部扫描,所以这些BeanDifinition已经被同步。
但是bean1的条件注解依赖的是bean2,bean2是被定义的配置类中的,所以此时配置类的解析无法保证先后顺序,就会出现不生效的情况。
同样的道理,如果依赖的是FeignClient,可以设想一下结果?FeignClient最终还是由配置类触发的,解析的先后顺序同样也不能保证。
解决
以下两种方式:
-
项目中条件注解依赖的类,大多会交给
spring容器管理,所以如果要在配置中Bean通过@ConditionalOnBean依赖配置中的Bean时,完全可以用@ConditionalOnClass(Bean2.class)来代替。 -
如果一定要区分两个配置类的先后顺序,可以将这两个类交与
EnableAutoConfiguration管理和触发。也就是定义在META-INF\spring.factories中声明是配置类,然后通过@AutoConfigureBefore、AutoConfigureAfter AutoConfigureOrder控制先后顺序。之所以这么做是因为这三个注解只对自动配置类的先后顺序生效。这里推荐第一种。
总结
在配置类中定义Bean,如果使用@ConditionalOnBean注解依赖的Bean是通过配置类触发解析的,则执行结果依赖配置类加载顺序
2484





