1. 背景介绍
在整个SpringBoot项目中关于Bean的注册,我们可能需要指示Bean只有在所有条件满足的情况下才有资格注册到容器中,比如说像下面这个例子,如果说我们已经注册过ViewResolver自然不需要SpringMVC帮我们注册了
上面的ConditionalOnBean
正是Conditional
的一种延伸
本文将围绕着SpringBoot中如何利用Conditional
及其子注解进行条件判断来讲解
2. @Conditional
我们先看看这个注解的样子,发现这个接口其实就是传入一个Condition
类作为检查类
...
public @interface Conditional {
/**
* 检查类
*/
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 beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
spec.getIgnoredTypes(), parameterizedContainers);
// 类型的匹配
for (String type : spec.getTypes()) {
// 获得指定类型Bean的名称
Collection 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 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 onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
// 对传入的类进行过滤,返回不能加载的类
List 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 onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
// 对传入的类进行过滤,返回能加载的类
List 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- ...