[SpringBoot源码分析二]:@Condition
1. 背景介绍
在整个SpringBoot项目中关于Bean的注册,我们可能需要指示Bean只有在所有条件满足的情况下才有资格注册到容器中,比如说像下面这个例子,如果说我们已经注册过ViewResolver自然不需要SpringMVC帮我们注册了
上面的ConditionalOnBean
正是Conditional
的一种延伸
本文将围绕着SpringBoot中如何利用Conditional
及其子注解进行条件判断来讲解
2. @Conditional
我们先看看这个注解的样子,发现这个接口其实就是传入一个Condition
类作为检查类
...
public @interface Conditional {
/**
* 检查类
*/
Class<? extends Condition>[] value();
}
而Condition
也很简单,就只有一个matches方法,根据返回值判断是否匹配成功
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
@Conditional
和Condition
长的非常像,事实上呢这两也是一一对应的,比如说@ConditionalOnBean
对应的就是OnBeanCondition
SpringBoot中支持的@Conditional
注解非常的多,接下来我会逐一讲解下面的注解
3. ConditionEvaluator
这里为什么要介绍这个类呢,前面我只是介绍了这些注解的作用以及对应的检查类,但是SpringBoot是如何判断一个Bean需要执行检查的呢,又是怎么判断需要通过哪个检查类去检查呢? 没错靠ConditionEvaluator
紧接着我们看ConditionEvaluator
是在哪里执行的,我这里只举一个例子,当我们的候选配置类都被包装为ConfigurationClass
然后转为BeanDefinitions
的时候会执行loadBeanDefinitionsForConfigurationClass(...)方法, 大家可以看到这里首当其冲的进行了检查
大家这里不用纠结什么ConfigurationClass
和BeanDefinitions
,只需要记住Bean在生命周期会通过ConditionEvaluator
去确定Bean需要检查什么类型
接着我们看ConditionEvaluator
的核心方法shouldSkip(...)
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 没有携带 Conditional 的注解不需要跳过
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);
}
//再递归调用:REGISTER_BEAN:表示无论条件是否成立都加入
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
//获取当前类上有关@Conditional注解的信息
//是一个List<String[]>,list表示可能有多个@Conditional注解
for (String[] conditionClasses : getConditionClasses(metadata)) {
//其中String[]是因为@Conditional是可以传入多个Class,其他的衍生类,就不行了,指定了一个了
for (String conditionClass : conditionClasses) {
//将当前类所标志的@Conditional类型的注解实例化,加入候选集合中
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
//是利用AnnotationAwareOrderComparator进行排序,
//这是一个针对Ordered接口、PriorityOrdered接口和@Order注解进行排序的
AnnotationAwareOrderComparator.sort(conditions);
//遍历所有的@Conditional注解,看能否满足条件
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;
}
虽然说这个方法很长,但是当我精简下,大家就明白了:
- 如若元数据没有带有
@Conditional
及其子注解就直接返回 - 一旦发现了,就把上面带有的检查类拿出来,进行实例化
- 将所有检查类进行排序,其排序逻辑正是检查类上带有的
@Order
注解 - 然后调用检测类的
matches(...)
方法进行检查,如果有任何一个检查类匹配失败,则认为需要跳过
这里对部分@Condition
子注解和对应的检查类做一个归类,方便大家理解
@ConditionalOnBean
:OnBeanCondition
@ConditionalOnClass
:OnClassCondition
@ConditionalOnWebApplication
:OnWebApplicationCondition
- ....
4. @ConditionalOnBean
@ConditionalOnBean
:要求容器中必须存在某种Bean,其检查类是OnBeanCondition
在介绍OnBeanCondition
之前呢,我们得先了解下这个类的结构
我们可以发现它实现了两个重要的接口
SpringBootCondition
FilteringSpringBootCondition
我们先看SpringBootCondition
,它实现了Condition
接口,本质上就是基类,定义了matches(...)
方法,最终的匹配结果其实是getMatchOutcome(...)
方法决定的
public abstract class SpringBootCondition implements Condition {
...
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取全路径(包名 + 类名)
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 获得匹配结果
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
// 记录条件评估的发生情况
recordEvaluation(context, classOrMethodName, outcome);
// 返回匹配结果
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
...
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}
...
}
紧接着我们看FilteringSpringBootCondition
类,大家细心的话就会发现这个类还实现了AutoConfigurationImportFilter
接口,大家看名字就知道这是一个关于自动配置类的匹配类
abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
...
/**
* 返回自动配置类评估结果
* @param autoConfigurationClasses 自动配置候选类
* @param autoConfigurationMetadata 自动配置类规则
* @return
*/
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
// 获得评估结果, 某个元素为空代表匹配成功
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
// 匹配失败的情况
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
// 记录条件评估的发生情况
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
}
至于为什么要多定义一个match
方法呢,我理解如下:
- 方法的入参不一样
- 由于
spring.factories
和spring-autoconfigure-metadata.properties
文件中可能存在多个自动配置类以及规则,那么返回的类型肯定需要是一个数组或者集合了
刚才我说了matches(...)
的匹配结果是靠getMatchOutcome(...)
方法决定的,所以我们直接看这个方法在OnBeanCondition
中的实现
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
// 检查 ConditionalOnBean 情况
if (annotations.isPresent(ConditionalOnBean.class)) {
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
// 返回匹配结果
MatchResult matchResult = getMatchingBeans(context, spec);
// 无法匹配成功
if (!matchResult.isAllMatched()) {
// 创建无法匹配成功的原因
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
// 到这就说明条件成立,封装返回消息
matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
// 检查 ConditionalOnSingleCandidate 情况
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
// 返回匹配结果
MatchResult matchResult = getMatchingBeans(context, spec);
// 先判断是否有此Bean
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
}
// 再看是否只存在一个候选Bean,或者只有一个主要候选Bean
else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
spec.getStrategy() == SearchStrategy.ALL)) {
return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
}
// 到这就说明条件成立,封装返回消息
matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
// 检查 ConditionalOnMissingBean 情况
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
ConditionalOnMissingBean.class);
// 返回匹配结果
MatchResult matchResult = getMatchingBeans(context, spec);
// 如果匹配成功,在这个注解的情况下,表示不能加入容器中
if (matchResult.isAnyMatched()) {
// 创建的 @OnMissingBean 的无法匹配结果
String reason = createOnMissingBeanNoMatchReason(matchResult);
// 返回不匹配的信息
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
}
// 返回匹配成功的信息
return ConditionOutcome.match(matchMessage);
}
虽然说这个方法看起来很长,其实很简单,由于OnBeanCondition
这个检查类是支持多个注解的@ConditionalOnBean
、@ConditionalOnSingleCandidate
、@ConditionalOnMissingBean
,所以这里封装为了Spec
类,然就调用了getMatchingBeans(...)
方法
然后我们看了getMatchingBeans(...)
方法,其实就是针对Bean类型、Bean名称,注解进行匹配
protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
ClassLoader classLoader = context.getClassLoader();
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
// 是否在所有父容器中查找
if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
"Unable to use SearchStrategy.ANCESTORS");
beanFactory = (ConfigurableListableBeanFactory) parent;
}
MatchResult result = new MatchResult();
// 获得所有可以忽略的Bean名称
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
spec.getIgnoredTypes(), parameterizedContainers);
// 类型的匹配
for (String type : spec.getTypes()) {
// 获得指定类型Bean的名称
Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
parameterizedContainers);
// 移除需要忽略的
typeMatches.removeAll(beansIgnoredByType);
// 记录匹配不成功或者成功
if (typeMatches.isEmpty()) {
result.recordUnmatchedType(type);
}
else {
result.recordMatchedType(type, typeMatches);
}
}
// 注解的匹配,和类型的匹配差不多
for (String annotation : spec.getAnnotations()) {
Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
considerHierarchy);
annotationMatches.removeAll(beansIgnoredByType);
if (annotationMatches.isEmpty()) {
result.recordUnmatchedAnnotation(annotation);
}
else {
result.recordMatchedAnnotation(annotation, annotationMatches);
}
}
// 名称的匹配,和类型的匹配差不多
for (String beanName : spec.getNames()) {
if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
result.recordMatchedName(beanName);
}
else {
result.recordUnmatchedName(beanName);
}
}
return result;
}
5. @ConditionalOnClass
@ConditionalOnClass
:检查类是OnClassCondition
,要求能够通过ClassLoader
加载到指定的类
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
// ConditionalOnClass的情况
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
// 对传入的类进行过滤,返回不能加载的类
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
// 匹配失败:有无法加载的类
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
}
// 匹配成功
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes")
.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
// ConditionalOnMissingClass的情况
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
// 对传入的类进行过滤,返回能加载的类
List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
// 匹配失败:有可以加载的类
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
}
// 匹配成功
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes")
.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
}
return ConditionOutcome.match(matchMessage);
}
其实这个类没什么好讲的,但是这个类实现了FilteringSpringBootCondition
, 这里针对于自动配置类的过滤,做了特殊的设置,那么紧接着就来看看
大家看我的注释就知道,这里可以通过两个线程进行检查自动配置类
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
//如果系统内核大于1,就多开一个线程进行匹配
if (Runtime.getRuntime().availableProcessors() > 1) {
return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
}
//一个线程进行匹配
else {
OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
return outcomesResolver.resolveOutcomes();
}
}
resolveOutcomesThreaded(...)方法中会将自动配置类分为两部分,分别交给了StandardOutcomesResolver
和ThreadedOutcomesResolver
去处理
private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// 确定第一个线程的结束位置和第二个线程的开始位置
int split = autoConfigurationClasses.length / 2;
// 创建一个多线程的匹配类
OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
autoConfigurationMetadata);
// 创建第二个用当前线程的匹配类
OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
// 开始匹配
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
// 封账匹配结果,并返回
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes;
}
StandardOutcomesResolver
其实就很简单就for循环检查,而ThreadedOutcomesResolver
就会利用多线程去进行匹配
6. 其他注解
至于其他的注解其实不太常见,我这就就简单做个介绍
@OnExpressionCondition
:支持以SpEL表达式去解析@ConditionalOnJava
:要求JVM版本范围的@ConditionalOnWebApplication
:要求当前Web程序类型,是Servlet还是Reactive- ...
转载自:https://juejin.cn/post/7278849299260915766