专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

SpringBoot源码解析-@ConditionalOnXXX注解原理

上一节讲到springboot自动化配置是以@Conditional相关注解作为判断条件,那么这一节我们来了解一下@Conditional相关注解的原理。


@Conditional使用示范

新建一个ControllerConditional类,实现Condition接口,实现matches方法,返回false

public class ControllerConditional implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

在Controller类上添加@Conditional(ControllerConditional.class)注解

@RestController
@Conditional(ControllerConditional.class)
public class Controller {

    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }

}

在main函数中尝试获取Controller类。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        String[] beanNamesForType = context.getBeanNamesForType(Controller.class);
        System.out.println(Arrays.toString(beanNamesForType));
    }

}

不出意外控制台会打印出空数组[]。此时去掉Controller类上的@Conditional(ControllerConditional.class)注解,控制台又可以打印出[controller]

@Conditional注解的原理

经过上面的简单示例,对于@Conditional注解的使用大家应该清楚了,如果matches方法返回false,那么这个类就不会被扫描,反之则会被扫描进spring容器。下面就来了解一下他们的原理。

回到上一节我们讲解析Component,PropertySources,ComponentScan这几个注解的地方,进入processConfigurationClass方法,发现在解析之前有一行代码。

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }

shouldSkip方法就是判断@Conditional注解的地方(这个shouldSkip方法其他地方也有,但是基本原理都是一样的,或者说就是一样的),在进入之前,我们先了解一下他的参数以及conditionEvaluator。找到当前类的构造函数,发现如下信息。

    public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
            ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
            BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {

        ...
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
    }

    public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
            @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

        this.context = new ConditionContextImpl(registry, environment, resourceLoader);
    }

构造函数不复杂,应该没啥问题。接下来了解一下shouldSkip方法的两个参数,顺着方法找回去。

this.metadata = new StandardAnnotationMetadata(beanClass, true);

    public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
        super(introspectedClass);
        this.annotations = introspectedClass.getAnnotations();
        this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
    }

metadata就是这边的StandardAnnotationMetadata,第二个参数是一个枚举。做好这些准备工作后,开始进入shouldSkip方法。

    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
        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);
            }
            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
        }
        //获取该类的所有@Conditional注解里面的参数类
        List<Condition> conditions = new ArrayList<>();
        for (String[] conditionClasses : getConditionClasses(metadata)) {
            for (String conditionClass : conditionClasses) {
                Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                conditions.add(condition);
            }
        }

        AnnotationAwareOrderComparator.sort(conditions);

        for (Condition condition : conditions) {
            ConfigurationPhase requiredPhase = null;
            if (condition instanceof ConfigurationCondition) {
                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
            }
            //依次判断每个类的matches方法,有一个方法返回false则跳过这个类
            if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
                return true;
            }
        }

        return false;
    }

shouldSkip方法的逻辑不复杂,获取所有conditional注解里的参数类,依次调用matches方法,如果任意方法返回false则跳过该类。所以在这儿,我们就看到了matches方法的参数以及调用。这样的话,conditional注解的原理大家应该没啥问题了。

那么下面通过举例来看看由conditional注解衍生出的ConditionalOnXXX类型注解。

@ConditionalOnClass注解的原理

打开ConditionalOnClass注解的源代码,本身带有两个属性,一个class类型的value,一个String类型的name。同时ConditionalOnClass注解本身还带了一个@Conditional(OnClassCondition.class)注解。所以,其实ConditionalOnClass注解的判断条件就在于OnClassCondition这个类的matches方法。

@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

    Class<?>[] value() default {};

    String[] name() default {};

}

所以没啥好说的,直接进入OnClassCondition类,寻找matches方法。最终,在他的父类SpringBootCondition中,找到了matches方法。代码如下:

@Override
    public final boolean matches(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        //获取加上了@ConditionalOnClass注解的类或者方法的名称(我们就以类分析,加在方法上是一个原理)
        String classOrMethodName = getClassOrMethodName(metadata);
        try {
            //获取匹配结果
            ConditionOutcome outcome = getMatchOutcome(context, metadata);
            logOutcome(classOrMethodName, outcome);
            recordEvaluation(context, classOrMethodName, outcome);
            return outcome.isMatch();
        }
        ...
    }

从代码不难看出,关键方法在getMatchOutcome里,所以进入该方法。

@Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        ClassLoader classLoader = context.getClassLoader();
        ConditionMessage matchMessage = ConditionMessage.empty();
        //获取所有需要判断是否存在的类
        List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
        if (onClasses != null) {
            //筛选这些类,判断条件为ClassNameFilter.MISSING
            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));
        }
        ...
        return ConditionOutcome.match(matchMessage);
    }

该方法并不复杂,和ConditionalOnClass有关的代码主要有两行,getCandidates和filter。 首先看看getCandidates:

private List<String> getCandidates(AnnotatedTypeMetadata metadata,
            Class<?> annotationType) {
        MultiValueMap<String, Object> attributes = metadata
                .getAllAnnotationAttributes(annotationType.getName(), true);
        if (attributes == null) {
            return null;
        }
        List<String> candidates = new ArrayList<>();
        addAll(candidates, attributes.get("value"));
        addAll(candidates, attributes.get("name"));
        return candidates;
    }

主要是获取了ConditionalOnClass的name属性和value属性。

接下来看看filter方法,在进入filter方法前,先看一下判断条件ClassNameFilter.MISSING

        MISSING {

            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return !isPresent(className, classLoader);
            }

        };

        public static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }
            try {
                forName(className, classLoader);
                return true;
            }
            catch (Throwable ex) {
                return false;
            }
        }

        private static Class<?> forName(String className, ClassLoader classLoader)
                throws ClassNotFoundException {
            if (classLoader != null) {
                return classLoader.loadClass(className);
            }
            return Class.forName(className);
        }

逻辑很清晰,如果该类能被加载则判断成功,否则判断失败。现在进入filter方法。

    protected 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) {
            //逐个判断我们添加的判断条件,如果有不符合的即添加进list
            if (classNameFilter.matches(candidate, classLoader)) {
                matches.add(candidate);
            }
        }
        return matches;
    }

filter方法就是利用刚刚的判断条件进行判断,发现不符合的添加进list一并返回,最后生成结果。

所以到这儿,conditional相关注解的原理应该都清楚了,其他衍生类原理也大多相似,就不再一一分析。


返回目录

文章永久链接:https://tech.souyunku.com/19190

未经允许不得转载:搜云库技术团队 » SpringBoot源码解析-@ConditionalOnXXX注解原理

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们