springboot 自动装配之条件注解(一)

572 阅读9分钟

注:本系列源码分析基于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的使用方式:

  1. @Conditional 是 spring 处理的条件注解;
  2. @Conditional 提供了一个属性value,类型为Class,其必须是Condition的子类:
    Class<? extends Condition>[] value();
    
  3. Condition 是一个接口,其中有一个matches(...)方法:
    public interface Condition {
    
        boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    
    }
    
    只有在matches(...)方法返回 true 时,ConfigurationClassPostProcessor 才会将其对应的 bean 注册到 beanFactorybeanDefinitionMap 中.

总结完@Conditional的使用方式后,我们就明白了:OnClassCondition.classCondition 的子类,其matches(...) 方法用来处理主要条件规则。同理,其他条件注解的处理方式也类似,这里总结下条件注解的判断类:

注解类型注解类型条件判断类
class 条件注解@ConditionalOnClass/@ConditionalOnMissingClassOnClassCondition
bean 条件注解@ConditionalOnBean/@ConditionalOnMissingBeanOnBeanCondition
属性条件注解@ConditionalOnPropertyOnPropertyCondition
Resource 条件注解@ConditionalOnResourceOnResourceCondition
Web 应用条件注解@ConditionalOnWebApplication / @ConditionalOnNotWebApplicationOnWebApplicationCondition
spring表达式条件注解@ConditionalOnExpressionOnExpressionCondition

接下来,分析目的就很明确了:要分析这些条件注解的判断逻辑,只需要分析对应条件判断类的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);

    ...

}

SpringBootConditionmatches(...) 关键就两行:

...
ConditionOutcome outcome = getMatchOutcome(context, metadata);
...
return outcome.isMatch();

SpringBootConditiongetMatchOutcome(...) 又是个抽象方法,具体的逻辑由子类提供,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;
    }

    ...
}

从代码来看,这个类就是用来封装比较结果的,内部有两个属性:matchmessage:

  • 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时,classNameFilterClassNameFilter.MISSING
  • 处理@ConditionalOnMissingClass时,classNameFilterClassNameFilter.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,处理步骤如下:

  1. 获取 ignoreType,只有 @ConditionalOnMissingBean 有这个属性
  2. 处理 types 的匹配规则
  3. 处理注解(类上的注解)的匹配规则, 只有 @ConditionalOnMissingBean 有这个属性
  4. 处理 beanName 的匹配规则

关于以上步骤的具体细节,本文就不具体展开了,这里仅提供流程:

  1. 获取 ignoreType
    1. 使用 ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean)方法获取容器中所有的ignoreTypebeanName
    2. 结果为beansIgnoredByType(类型是Set<String>)
  2. 处理 types 的匹配规则
    1. 使用ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean)方法获取容器中所有的type对应的beanName,结果为typeMatches
    2. typeMatches中的值去除ignoreType
    3. 判断第二步得到的typeMatches,如果内容为空,将当前Type保存到unmatchedTypes中,否则保存到matchedTypesnamesOfAllMatches
  3. 处理注解的匹配规则
    1. 使用ListableBeanFactory#getBeanNamesForAnnotation方法获取容器中所有的annotation对应的beanName,结果为annotationMatches
    2. annotationMatches中的值去除ignoreType
    3. 判断第二步得到的annotationMatches,如果内容为空,将当前Annotation保存到unmatchedAnnotations中,否则保存到matchedAnnotationsnamesOfAllMatches
  4. 处理beanName的匹配规则
    1. 判断 beansIgnoredByType 是否包含beanName
    2. 使用BeanFactory#containsBean方法判断容器中有该beanName
    3. 如果第2步结果为false,第二步结果为true,则将当前beanName加入到matchedNamesnamesOfAllMatches,否则保存到unmatchedNames

得到matchedTypesunmatchedNames等内容后,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#getBeanNamesForTypeBeanFactory#containsBean来判断beanNamebeanType是否存在了。

在使用@ConditionalOnBean/@ConditionalOnMissingBean时,有一个坑需要特别注意:条件注解的执行时机是在spring的ConfigurationClassPostProcessor中的,确切地说,是在将bean加入到beanFactorybeanDefinitionMap之前判断的,如果满足条件则添加到beanDefinitionMap中,否则就不添加。这样就导致了一个问题:如果在@ConditionalOnBean/@ConditionalOnMissingBeanbean在该bean之后加入到beanDefinitionMap中,就有可能出现误判,举例说明:

现在有两个类:

@Component
@ConditionalOnMissingBean("b")
public class A {

}

@Component
public class B {

}

其中AB都添加了@Component,表明这是spring bean,然后在A上添加了注解@ConditionalOnMissingBean("b"),表明在b不存在时,A才进行初始化。有了这些前提,我们再来看看两种情况:

  1. 如果b先添加到beanDefinitionMap中,在将a添加到beanDefinitionMap时,发现b已经存在了,于是就不添加了,符合我们的预期;
  2. 如果a先被处理,在添加时,发现beanDefinitionMap中并没有b,于是a被添加到beanDefinitionMap中,再处理bb 也会被添加到beanDefinitionMap,这样一来,ab同时存在于beanDefinitionMap中,最终都会被初始化成spring bean,这与我们的预期不符。

那么springboot如何解决以上问题呢?我们来看看@ConditionalOnBean/@ConditionalOnMissingBean的说明:

稍微翻译如下:

该条件只能匹配到目前为止的应用程序上下文中的bean存在情况,因此,强烈建议仅于自动配置类中使用。如果候选bean要在另一种自动配置下创建,请确保使用此条件的配置在此之后运行。

对以上内容,我的解读如下:

  • @ConditionalOnBean/@ConditionalOnMissingBean 标记 bean加入到beanDefinitionMap那一刻,仅匹配目前为止 beanDefinitionMap 中已存在的bean,对之后加入的bean不考虑,这就有可能造成误判,可以参考上面举的ab的例子
  • 强烈建议仅在自动配置类中使用@ConditionalOnBean/@ConditionalOnMissingBean这两个注解,也就是说在自动配置类中使用的话,能正确处理匹配
  • 还是拿上面的ab举例,如果ab分别位于不同的自动配置类中,那么a需要在b之后加载到beanDefinitionMap中,这个可以通过@AutoConfigureAfter@AutoConfigureBefore@AutoConfigureOrder等注解来指定

关于自动配置类的加载顺序,后面再做分析吧。

限于篇幅,本文就先到这里了,下篇继续分析剩下的条件注解。


本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

【springboot源码分析】springboot源码分析系列文章汇总