注:本系列源码分析基于springboot 2.2.2.RELEASE,对应的spring版本为5.2.2.RELEASE,源码的gitee仓库仓库地址:gitee.com/funcy/sprin….
1. 条件注解及其判断类
在springboot 自动装配之加载自动装配类一文中,我们分析到 springboot 会加载META-INF/spring.factories文件中定义的自动装配类,加载到这些自动装配类后,这些类中的bean就一定会初始化吗?并不是,我们可以在对应的 Bean 生成方法上使用条件注解来控制类是否进行初始化!
springboot 提供的条件注解如下:
这里列举部分如下:
| 注解类型 | 注解类型 | 功能说明 |
|---|---|---|
| class 条件注解 | @ConditionalOnClass/@ConditionalOnMissingClass | 当指定的类存在/缺失时初始化该 bean |
| bean 条件注解 | @ConditionalOnBean/@ConditionalOnMissingBean | 当指定的bean存在/缺失时初始化该 bean |
| 属性条件注解 | @ConditionalOnProperty | 当指定的属性存在初始化该 bean |
| Resource 条件注解 | @ConditionalOnResource | 当指定的资源存在初始化该 bean |
| Web 应用条件注解 | @ConditionalOnWebApplication / @ConditionalOnNotWebApplication | 当前应用为/不为web应用时初始化该 bean |
| spring表达式条件注解 | @ConditionalOnExpression | 当表达式结果为true时初始化该 bean |
我们进入@ConditionalOnClass看看该注解的内容:
...
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
...
}
可以看到,ConditionalOnClass 组合了@Conditional注解的功能,处理类是OnClassCondition.class。
关于@Conditional注解可以参考ConfigurationClassPostProcessor之处理@Conditional注解,这里我们直接说@Conditional的使用方式:
@Conditional是 spring 处理的条件注解;@Conditional提供了一个属性value,类型为Class,其必须是Condition的子类:Class<? extends Condition>[] value();Condition是一个接口,其中有一个matches(...)方法:只有在public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }matches(...)方法返回true时,ConfigurationClassPostProcessor才会将其对应的 bean 注册到beanFactory的beanDefinitionMap中.
总结完@Conditional的使用方式后,我们就明白了:OnClassCondition.class 是 Condition 的子类,其matches(...) 方法用来处理主要条件规则。同理,其他条件注解的处理方式也类似,这里总结下条件注解的判断类:
| 注解类型 | 注解类型 | 条件判断类 |
|---|---|---|
| class 条件注解 | @ConditionalOnClass/@ConditionalOnMissingClass | OnClassCondition |
| bean 条件注解 | @ConditionalOnBean/@ConditionalOnMissingBean | OnBeanCondition |
| 属性条件注解 | @ConditionalOnProperty | OnPropertyCondition |
| Resource 条件注解 | @ConditionalOnResource | OnResourceCondition |
| Web 应用条件注解 | @ConditionalOnWebApplication / @ConditionalOnNotWebApplication | OnWebApplicationCondition |
| spring表达式条件注解 | @ConditionalOnExpression | OnExpressionCondition |
接下来,分析目的就很明确了:要分析这些条件注解的判断逻辑,只需要分析对应条件判断类的matches(...) 方法就可以了。
2. SpringBootCondition#matches
进入OnClassCondition#matches方法,发现来到的是SpringBootCondition,相关方法如下:
public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(getClass());
@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);
// 这里返回最终结果:true 或 false
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException(...);
}
catch (RuntimeException ex) {
throw new IllegalStateException(...);
}
}
/**
* 这是个抽象方式,具体内容由子类实现
*/
public abstract ConditionOutcome getMatchOutcome(
ConditionContext context, AnnotatedTypeMetadata metadata);
...
}
SpringBootCondition的matches(...) 关键就两行:
...
ConditionOutcome outcome = getMatchOutcome(context, metadata);
...
return outcome.isMatch();
而SpringBootCondition 的 getMatchOutcome(...) 又是个抽象方法,具体的逻辑由子类提供,OnClassCondition 它的实现之一。实际上,上述条件判断类都是SpringBootCondition 的子类,后面我们就直接进入具体类的getMatchOutcome(...) 方法分析了。
getMatchOutcome(...) 方法返回的结果是ConditionOutcome,接下来我们来看看ConditionOutcome是个啥:
public class ConditionOutcome {
private final boolean match;
private final ConditionMessage message;
/**
* 构造方法
*/
public ConditionOutcome(boolean match, String message) {
this(match, ConditionMessage.of(message));
}
/**
* 构造方法
*/
public ConditionOutcome(boolean match, ConditionMessage message) {
Assert.notNull(message, "ConditionMessage must not be null");
this.match = match;
this.message = message;
}
/**
* 返回匹配的结果
*/
public boolean isMatch() {
return this.match;
}
...
}
从代码来看,这个类就是用来封装比较结果的,内部有两个属性:match与message:
match的类型是boolean,这个就是最终匹配成功还是失败的标识message的类型是ConditionMessage,它表示匹配结果的说明
我们再来看看ConditionMessage:
public final class ConditionMessage {
private String message;
private ConditionMessage() {
this(null);
}
private ConditionMessage(String message) {
this.message = message;
}
...
}
它仅有一个属性:message,这表明它就是对说明信息的包装。
3. @ConditionalOnClass: OnClassCondition#getMatchOutcome
接下来我们来分析OnClassCondition的匹配逻辑,直接进入getMatchOutcome方法:
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
// 1. 处理 @ConditionalOnClass 注解
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
// 1.1 处理条件判断
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
if (!missing.isEmpty()) {
// 1.2 构建返回结果:不匹配的情况
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));
}
// 2. 处理 @ConditionalOnMissingClass 注解
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
// 2.1 处理条件判断
List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
if (!present.isEmpty()) {
// 2.2 构建返回结果:不匹配的情况
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);
}
这个方法同时处理了@ConditionalOnClass 与 @ConditionalOnMissingClass 两个注解,处理流程极其相似,两个注解的条件判断都是通过FilteringSpringBootCondition#filter内容如下:
protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
ClassLoader classLoader) {
if (CollectionUtils.isEmpty(classNames)) {
return Collections.emptyList();
}
List<String> matches = new ArrayList<>(classNames.size());
for (String candidate : classNames) {
// 进行条件匹配
if (classNameFilter.matches(candidate, classLoader)) {
matches.add(candidate);
}
}
return matches;
}
由此可见,传入的classNameFilter成了关键:
- 处理
@ConditionalOnClass时,classNameFilter为ClassNameFilter.MISSING - 处理
@ConditionalOnMissingClass时,classNameFilter为ClassNameFilter.PRESENT
让我们进入ClassNameFilter一探究竟,它是FilteringSpringBootCondition的子类,内容如下:
abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
...
/**
* 如果 classLoader 存在,则调用 ClassLoader#loadClass 方法
* 否则调用 Class#forName 方法
*/
protected static Class<?> resolve(String className, ClassLoader classLoader)
throws ClassNotFoundException {
if (classLoader != null) {
return classLoader.loadClass(className);
}
return Class.forName(className);
}
/**
* 处理条件匹配
*/
protected enum ClassNameFilter {
PRESENT {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return isPresent(className, classLoader);
}
},
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
abstract boolean matches(String className, ClassLoader classLoader);
/**
* Class 是否存在
* 通过捕获类加载时的异常来判断类是否存在,未抛出异常则表示类存在
*/
static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
// 通过异常捕获来判断是否存在该class
resolve(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;
}
}
}
...
}
看到这里我们就明白了:判断Class是否存在,spring是通过捕获ClassLoader.load(String)或Class.forName(String)方法的异常来处理的,如果抛出了异常就表明Class不存在。
这里总结下@ConditionalOnClass/@ConditionalOnMissingClass的处理方式:两者的处理类都为OnClassCondition,通过捕获ClassLoader.load(String)或Class.forName(String)方法的异常来判断Class是否存在,如果抛出了异常就表明Class不存在。
4. @ConditionalOnBean: OnBeanCondition#getMatchOutcome
继续看看@ConditionalOnBean的 处理,直接进入OnBeanCondition#getMatchOutcome:
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);
// 注意判断条件
if (!matchResult.isAllMatched()) {
return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
}
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()) {
String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
}
可以看到,这个方法一共处理了两个注解的条件匹配:@ConditionalOnBean、@ConditionalOnSingleCandidate 与 @ConditionalOnMissingBean,三者都调用了同一个方法 getMatchingBeans(...) 来获取匹配结果,然后使用matchResult.isAllMatched()或matchResult.isAnyMatched()来做最终的结果判断。
OnBeanCondition#getMatchingBeans
getMatchingBeans(...)的代码如下:
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();
// 1. 获取 ignoreType,只有 @ConditionalOnMissingBean 有这个属性
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory,
considerHierarchy, spec.getIgnoredTypes(), parameterizedContainers);
// 2. 处理 types
for (String type : spec.getTypes()) {
Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy,
beanFactory, type, parameterizedContainers);
typeMatches.removeAll(beansIgnoredByType);
if (typeMatches.isEmpty()) {
result.recordUnmatchedType(type);
}
else {
result.recordMatchedType(type, typeMatches);
}
}
// 3. 处理类上的注解 @ConditionalOnMissingBean 有这个属性
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);
}
}
// 4. 处理 beanName
for (String beanName : spec.getNames()) {
if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName,
considerHierarchy)) {
result.recordMatchedName(beanName);
}
else {
result.recordUnmatchedName(beanName);
}
}
return result;
}
需要说明的是,这个方法会处理3个注解的匹配规则:@ConditionalOnBean、@ConditionalOnSingleCandidate 与 @ConditionalOnMissingBean,处理步骤如下:
- 获取
ignoreType,只有@ConditionalOnMissingBean有这个属性 - 处理
types的匹配规则 - 处理注解(类上的注解)的匹配规则, 只有
@ConditionalOnMissingBean有这个属性 - 处理
beanName的匹配规则
关于以上步骤的具体细节,本文就不具体展开了,这里仅提供流程:
- 获取
ignoreType:- 使用
ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean)方法获取容器中所有的ignoreType的beanName - 结果为
beansIgnoredByType(类型是Set<String>)
- 使用
- 处理
types的匹配规则- 使用
ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean)方法获取容器中所有的type对应的beanName,结果为typeMatches - 将
typeMatches中的值去除ignoreType - 判断第二步得到的
typeMatches,如果内容为空,将当前Type保存到unmatchedTypes中,否则保存到matchedTypes与namesOfAllMatches中
- 使用
- 处理注解的匹配规则
- 使用
ListableBeanFactory#getBeanNamesForAnnotation方法获取容器中所有的annotation对应的beanName,结果为annotationMatches - 将
annotationMatches中的值去除ignoreType - 判断第二步得到的
annotationMatches,如果内容为空,将当前Annotation保存到unmatchedAnnotations中,否则保存到matchedAnnotations与namesOfAllMatches中
- 使用
- 处理
beanName的匹配规则- 判断
beansIgnoredByType是否包含beanName - 使用
BeanFactory#containsBean方法判断容器中有该beanName - 如果第2步结果为
false,第二步结果为true,则将当前beanName加入到matchedNames与namesOfAllMatches,否则保存到unmatchedNames中
- 判断
得到matchedTypes、unmatchedNames等内容后,matchResult.isAllMatched()或matchResult.isAnyMatched()最终的判断结果就是判断这些结构是否空:
boolean isAllMatched() {
return this.unmatchedAnnotations.isEmpty() && this.unmatchedNames.isEmpty()
&& this.unmatchedTypes.isEmpty();
}
boolean isAnyMatched() {
return (!this.matchedAnnotations.isEmpty()) || (!this.matchedNames.isEmpty())
|| (!this.matchedTypes.isEmpty());
}
看来,@ConditionalOnBean/@ConditionalOnMissingBean的关键,就是使用ListableBeanFactory#getBeanNamesForType或BeanFactory#containsBean来判断beanName、beanType是否存在了。
在使用@ConditionalOnBean/@ConditionalOnMissingBean时,有一个坑需要特别注意:条件注解的执行时机是在spring的ConfigurationClassPostProcessor中的,确切地说,是在将bean加入到beanFactory的beanDefinitionMap之前判断的,如果满足条件则添加到beanDefinitionMap中,否则就不添加。这样就导致了一个问题:如果在@ConditionalOnBean/@ConditionalOnMissingBean的bean在该bean之后加入到beanDefinitionMap中,就有可能出现误判,举例说明:
现在有两个类:
@Component
@ConditionalOnMissingBean("b")
public class A {
}
@Component
public class B {
}
其中A与B都添加了@Component,表明这是spring bean,然后在A上添加了注解@ConditionalOnMissingBean("b"),表明在b不存在时,A才进行初始化。有了这些前提,我们再来看看两种情况:
- 如果
b先添加到beanDefinitionMap中,在将a添加到beanDefinitionMap时,发现b已经存在了,于是就不添加了,符合我们的预期; - 如果
a先被处理,在添加时,发现beanDefinitionMap中并没有b,于是a被添加到beanDefinitionMap中,再处理b,b也会被添加到beanDefinitionMap,这样一来,a与b同时存在于beanDefinitionMap中,最终都会被初始化成spring bean,这与我们的预期不符。
那么springboot如何解决以上问题呢?我们来看看@ConditionalOnBean/@ConditionalOnMissingBean的说明:
稍微翻译如下:
该条件只能匹配到目前为止的应用程序上下文中的bean存在情况,因此,强烈建议仅于自动配置类中使用。如果候选bean要在另一种自动配置下创建,请确保使用此条件的配置在此之后运行。
对以上内容,我的解读如下:
- 被
@ConditionalOnBean/@ConditionalOnMissingBean标记bean加入到beanDefinitionMap那一刻,仅匹配目前为止beanDefinitionMap中已存在的bean,对之后加入的bean不考虑,这就有可能造成误判,可以参考上面举的a与b的例子 - 强烈建议仅在自动配置类中使用
@ConditionalOnBean/@ConditionalOnMissingBean这两个注解,也就是说在自动配置类中使用的话,能正确处理匹配 - 还是拿上面的
a与b举例,如果a与b分别位于不同的自动配置类中,那么a需要在b之后加载到beanDefinitionMap中,这个可以通过@AutoConfigureAfter、@AutoConfigureBefore、@AutoConfigureOrder等注解来指定
关于自动配置类的加载顺序,后面再做分析吧。
限于篇幅,本文就先到这里了,下篇继续分析剩下的条件注解。
本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。