Skip to content

ConditionalOn

@ConditionalOn注解

Spring Boot 默认提供了很多个 AutoConfiguration 类,显然不可能会全部引入。所以在自动装配时,系统会去类路径下寻找是否有对应的配置类。如果有对应的配置类,则按条件进行判断,决定是否需要装配。

Spring Boot 中提供了一系列的条件注解,常见的包括:

注解描述
@ConditionalOnProperty只有当所提供的属性为 true 时才会实例化 Bean
@ConditionalOnBean只有在当前上下文中存在某个对象时才会实例化 Bean
@ConditionalOnClass只有当某个 Class 位于类路径上时才会实例化 Bean
@ConditionalOnExpression只有当表达式求值为 true 的时候才会实例化 Bean
@ConditionalOnMissingBean只有在当前上下文中不存在某个对象时才会实例化 Bean
@ConditionalOnMissingClass只有当某个 Class 在类路径上不存在时才会实例化 Bean
@ConditionalOnNotWebApplication只有当应用不是 Web 应用时才会实例化 Bean

这些注解都定义在 org.springframework.boot.autoconfigure.condition 包中。

image-20240224184114807

@ConditionalOnClass原理

java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
    Class<?>[] value() default {};

    String[] name() default {};
}

该注解本身带有两个属性,一个 Class 类型的 value,一个 String 类型的 name,可以采用这两种方式中的任意一种来使用该注解。

同时 ConditionalOnClass 注解本身还带了一个 @Conditional(OnClassCondition.class) 注解。

所以, ConditionalOnClass 注解的判断条件其实就包含在 OnClassCondition 这个类中。

OnClassCondition 是 SpringBootCondition 的子类,而 SpringBootCondition 又实现了Condition 接口。Condition 接口只有一个 matches 方法,

java
@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

SpringBootCondition中的matches方法实现

java
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    String classOrMethodName = getClassOrMethodName(metadata); // 1

    try {
        ConditionOutcome outcome = this.getMatchOutcome(context, metadata); // 2
        this.logOutcome(classOrMethodName, outcome);
        this.recordEvaluation(context, classOrMethodName, outcome);
        return outcome.isMatch();
    } catch (NoClassDefFoundError var5) {
        throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);
    } catch (RuntimeException var6) {
        throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
    }
}
java
private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
    if (metadata instanceof ClassMetadata) {
        ClassMetadata classMetadata = (ClassMetadata)metadata;
        return classMetadata.getClassName();
    } else {
        MethodMetadata methodMetadata = (MethodMetadata)metadata;
        return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
    }
}

这里的 getClassOrMethodName 方法获取被添加了@ConditionalOnClass 注解的类或者方法的名称,而 getMatchOutcome 方法用于获取匹配的输出。

getMatchOutcome 方法实际上是一个抽象方法,需要交由 SpringBootCondition 的各个子类完成实现,这里的子类就是 OnClassCondition 类。

在 Spring Boot 中,@ConditionalOnClass 或者 @ConditionalOnMissingClass 注解对应的条件类都是 OnClassCondition,所以在 OnClassCondition 的 getMatchOutcome 中会同时处理两种情况。

java
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ClassLoader classLoader = context.getClassLoader();
        ConditionMessage matchMessage = ConditionMessage.empty();
        List<String> onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
        List onMissingClasses;
        if (onClasses != null) {
            onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);
            if (!onMissingClasses.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
            }

            matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.filter(onClasses, ClassNameFilter.PRESENT, classLoader));
        }

        onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
        if (onMissingClasses != null) {
            List<String> present = this.filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
            if (!present.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
            }

            matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
        }

        return ConditionOutcome.match(matchMessage);
    }

首先通过 getCandidates 方法获取了 ConditionalOnClass 的 name 属性和 value 属性。

然后通过 getMatches 方法将这些属性值进行比对,得到这些属性所指定的但在类加载器中不存在的类。如果发现类加载器中应该存在但事实上又不存在的类,则返回一个匹配失败的 Condition;

反之,如果类加载器中存在对应类的话,则把匹配信息进行记录并返回一个 ConditionOutcome。