首页 > 其他分享 >@ConditionalOnClass注解解析

@ConditionalOnClass注解解析

时间:2024-07-07 18:57:13浏览次数:18  
标签:ConditionalOnClass return classLoader 注解 解析 class metadata

文章目录

概要

springboot中各种@ConditionalXxx注解控制着Bean是否注册,只有满足了一定条件才会被注册到容器中。这些注解包含@ConditionalOnClass、@OnBeanCondition、@ConditionalOnProperty等等,这篇文章就和大家探究下这些@ConditionalXxx注解到底是如何生效的,我会试着分析其中一个注解@ConditionalOnClass生效的规则,只要看懂一个,其余的@ConditionalXxx注解生效规则各位自己就可以看懂分析。看此篇文章需要一定的spring容器的基础,需要了解ConfigurationClassPostProcessor这个类加载bean的逻辑(将从这个类开始分析,前面过程略过),看完此篇文件你会知道@Conditional是如何生效的,更快的了解@ConditionalXxx的作用和原理。

Bean注册过程

这个过程分两步:
1、spring容器ApplicationContext在启动的过程中会根据类路径进行扫描,扫描到类上面定义了@Component注解的类(注意这里的类上面定义@Component并非是必须显示定义,也可以通过一些注解带入,例如@Service注解标记在业务类上也会被注册的原因是@Service注解本身定义里面是携带了@Component注解,会被spring提取),会把这些类作为spring创建bean的来源。
2、@Conditional的value属性会导入一个class数组。有了bean的来源,如果此类上定义了@Conditional注解,那么需要匹配导入的class类中的matches方法,如果所有的都匹配成功,那么会注册这个bean,如果有一个没匹配上,那么不会注册此bean。

ConfigurationClassParser.processConfigurationClass方法

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
        if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);
            if (existingClass != null) {
                if (configClass.isImported()) {
                    if (existingClass.isImported()) {
                        existingClass.mergeImportedBy(configClass);
                    }

                    return;
                }

                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }

            SourceClass sourceClass = this.asSourceClass(configClass, filter);

            do {
                sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
            } while(sourceClass != null);

            this.configurationClasses.put(configClass, configClass);
        }
    }

每个bean的来源都要进入ConfigurationClassParser.processConfigurationClass方法(特殊bean除外),只有加入到this.configurationClasses中的类才能被注册为bean(后面会针对这个类生成BeanDefinition)。所以这里的if判断就成了关键,如果if判断能为true(需要shouldSkip方法返回是false),类才会真的加入到this.configurationClasses.put(configClass, configClass);中,否则此类将被跳过注册。

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationCondition.ConfigurationPhase phase) {
        if (metadata != null && metadata.isAnnotated(Conditional.class.getName())) {
            if (phase == null) {
                return metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata)metadata) ? this.shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION) : this.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
            } else {
                List<Condition> conditions = new ArrayList();
                Iterator var4 = this.getConditionClasses(metadata).iterator();

                while(var4.hasNext()) {
                    String[] conditionClasses = (String[])var4.next();
                    String[] var6 = conditionClasses;
                    int var7 = conditionClasses.length;

                    for(int var8 = 0; var8 < var7; ++var8) {
                        String conditionClass = var6[var8];
                        Condition condition = this.getCondition(conditionClass, this.context.getClassLoader());
                        conditions.add(condition);
                    }
                }

                AnnotationAwareOrderComparator.sort(conditions);
                var4 = conditions.iterator();

                Condition condition;
                ConfigurationCondition.ConfigurationPhase requiredPhase;
                do {
                    do {
                        if (!var4.hasNext()) {
                            return false;
                        }

                        condition = (Condition)var4.next();
                        requiredPhase = null;
                        if (condition instanceof ConfigurationCondition) {
                            requiredPhase = ((ConfigurationCondition)condition).getConfigurationPhase();
                        }
                    } while(requiredPhase != null && requiredPhase != phase);
                } while(condition.matches(this.context, metadata));

                return true;
            }
        } else {
            return false;
        }
    }

进入shouldSkip方法会发现判断的依据正是是否在类上定义了@Conditional注解,如果定义了此注解,那么会取出value属性导入的所有的class对象(class对象是Condition的子类),并把这些对象实例化,排序之后依次调用condition.matches判断的匹配结果,如果都匹配上了那么返回false,加入到configurationClasses中,只要有一个没匹配上那么会被跳过。

@ConditionalOnClass注解

springboot自动配置模块导入了很多 @ConditionalOnXxx注解注解,例如@ConditionalOnClass@OnBeanCondition@ConditionalOnProperty等等
在这里插入图片描述
看起来像是@Conditional注解的亲戚,实际上当我们随便点击进去一个注解就会发现,这个注解本身导入了@Conditional注解,而每个@Conditional注解引入的class对象并不相同,例如@ConditionalOnClass注解导入的类是OnClassCondition.class@OnBeanCondition导入的是OnBeanCondition.class

@ConditionalOnClass注解

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

    String[] name() default {};
}

上面我们分析类上面定义了@Conditional注解会被识别到,@ConditionalOnClass本身导入了@Conditional注解,所以类上面标记了@ConditionalOnClass也会被spring提取出来,提取的正是这个value属性,那么OnClassCondition.class类作为真正的匹配条件判断类。会去调用OnClassCondition类的matches方法,但是OnClassCondition类本身并未实现该方法,它的实现方法在其父类SpringBootCondition中实现。

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

        try {
            ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
            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);
        }
    }

这里的outcome.isMatch()决定了匹配结果,点进去会发现就是返回了this.match属性。在构建对象outcome中实际会调用子类OnClassCondition.getMatchOutcome方法。

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);
    }

this.getCandidates(metadata, ConditionalOnClass.class)这个方法会找出该类上@ConditionalOnClass注解导入的value和name并进行合并成onClasses,如果onClasses不为空那么执行filter方法,filter方法返回的onMissingClasses不为空的话直接返回ConditionOutcome.noMatch构建的ConditionOutcome对象,这个构建方法里面直接设置了match属性为false,匹配失败,该bean会被跳过。如果代码走到了ConditionOutcome.match(matchMessage);那么证明匹配成功,则match属性会被设置为true,该bean会被注册。

public static ConditionOutcome noMatch(ConditionMessage message) {
        return new ConditionOutcome(false, message);
    }

OnClassCondition.filter方法

protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter, ClassLoader classLoader) {
        if (CollectionUtils.isEmpty(classNames)) {
            return Collections.emptyList();
        } else {
            List<String> matches = new ArrayList(classNames.size());
            Iterator var5 = classNames.iterator();

            while(var5.hasNext()) {
                String candidate = (String)var5.next();
                if (classNameFilter.matches(candidate, classLoader)) {
                    matches.add(candidate);
                }
            }

            return matches;
        }
    }

filter方法中遍历刚才合并的onClasses属性,并遍历调用classNameFilter.matches(candidate, classLoader)方法,如果不能匹配上(取反操作),那么加入到matches返回中。这里的filter是上层传递过来的ClassNameFilter.MISSING

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

取反操作,本质是调用isPresent方法

static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }

            try {
                FilteringSpringBootCondition.resolve(className, classLoader);
                return true;
            } catch (Throwable var3) {
                return false;
            }
        }

FilteringSpringBootCondition.resolve方法

   protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
        return classLoader != null ? Class.forName(className, false, classLoader) : Class.forName(className);
    }

可以看到,这个匹配的逻辑就是去类加载@ConditionalOnClass注解value和name属性导入的class类名,如果被加载到则返回true,未被加载到则返回false,返回true则会加入到filter方法matches中。

所以@ConditionalOnClass注解作用是需要在我们项目环境中存在某个类或者某些类的时候才会去注册此bean。

总结

@ConditionalOnClass、@ConditionalOnMissingClass、@ConditionalOnBean这些是springboot自动配置模块中引入的注解,其目的是通过这些注解引入@Conditional注解,并设置对应的匹配解析类,从而实现控制当达到某些必要条件时才会生成bean。
需要注意的是
1、@Conditional本身输入spring context模块,可以认为自动配置模块中的这些注解是对@Conditional的扩展,这在自动装配的时候显得很重要,集成三方的模块注册bean通常需要满足某些条件下才进行某些bean的注册。
2、上面的代码中有@ConditionalOnMissingClass注解没有分析,实际上分析的思路是一样的,只不过这里它把代码也冗余到了getMatchOutcome方法中而已。
3、当类上具有多个@ConditionalOnXxx注解的时候,必须满足所有的匹配条件才能注册此bean。
4、分析完@ConditionalOnClass的逻辑大家可以根据这个思路分析其他的注解,本质上就是注册的匹配类不同而已,就是状态绕来绕去不太好理清思绪,可以选择多看几遍。
5、可以根据需要自己注册自己的@ConditionalOnXxx注解以实现定制化功能。

标签:ConditionalOnClass,return,classLoader,注解,解析,class,metadata
From: https://blog.csdn.net/wwj19941028/article/details/140246378

相关文章

  • 【手写数据库内核组件】01 解析树的结构,不同类型的数据结构组多层的链表树,抽象类型统
    不同类型的链表​专栏内容:postgresql使用入门基础手写数据库toadb并发编程个人主页:我的主页管理社区:开源数据库座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.文章目录不同类型的链表概述1.数据类型识别1.1TLV格式介绍1.2结构体分层定义1.3定义......
  • 【Three.js 分子/晶体结构解析教程】
    Three.js分子/晶体结构解析教程环境准备最终效果思路解析1、基本配置2、解析结构3、进行解析1)原子部分2)连健部分3)晶格部分4)绘制位置调整5)绘制环境准备目前使用的Three.js版本是0.165.0npmithree最终效果结构化学式Al4As14思路解析绘制分子/晶体结构首先......
  • 深入解析RocketMQ的存储设计艺术(一)
    1. DomainModel领域模型(DomainModel)是对领域内的概念类或现实世界中对象的可视化表示。又称概念模型、领域对象模型、分析对象模型。它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。1.1 MessageMessage是RocketMQ消息引擎中的主体。mes......
  • Redis基本命令源码解析-字符串命令
    1.set用于将kv设置到数据库中2.mset批量设置kvmset(msetnx)key1value1key2value2...mset:msetCommandmsetnx:msetnxCommandmsetCommand和msetnxCommand都调用msetGenericCommand2.1msetGenericCommand如果参数个数为偶数,则响应参数错误并返回如果nx=1,则......
  • 【数据分析】RFM会员价值度模型详解:大案例解析(第28天)
    系列文章目录RFM会员价值度模型分析用户行为分析文章目录系列文章目录前言1RFM会员价值度模型分析案例1.1RFM会员价值度模型概念1.2RFM会员价值度模型实现流程1.3RFM案例代码实现1.4数据可视化1.5案例结论1.6结果保存2用户行为分析案例2.1用户行为分析概念2......
  • 【深度解析】Zxing:开源条形码图像处理库的领航者
                    在数字化浪潮席卷全球的今天,二维码和条形码已经成为我们日常生活中不可或缺的一部分,从超市购物到移动支付,从文档管理到物流追踪,它们无处不在。而在这背后,有一个开源项目默默地支撑着这一切——Zxing,一个强大的条形码图像处理库。     ......
  • mysql执行查询的过程解析
    mysql执行查询的过程客户端先发送查询语句给服务器服务器检查缓存,如果存在则返回进行sql解析,生成解析树,再预处理,生成第二个解析树,最后再经过优化器,生成真正的执行计划根据执行计划,调用存储引擎的API来执行查询将结果返回给客户端。一、客户端到服务端之间的原理客户端和服......
  • 深度解析:机器学习与深度学习的关系与区别
    一、前言在人工智能领域,机器学习与深度学习常常被提及并广泛应用。虽然它们在本质上都是通过数据训练模型以进行预测或分类,但两者之间存在着显著的区别和联系。本文将深入解析机器学习与深度学习的关系与区别,帮助读者更好地理解和应用这两种技术。二、机器学习概述定义机器......
  • 注解
    注解AnnotationAnnotation作用:不是程序本身,对程序作出解释(这一点和注释(comment)没什么区别)可以被其他程序(如:编译器)读取格式:注解是以"@注解名''在代码中存在的,还可以添加一些参数值,如:@SuppressWarings(value="unchecked")在哪里使用:可以附加在packa......
  • Spring的@Value注解和SpringBoot yml配置项目实战踩坑总结
    知识点Spring提供了@Value注解,可用于将配置文件或注册中心的属性值动态注入到Bean中。注:@Value注解在spring-beans包里。@Value("${...}"):注入获取对应属性文件中定义的属性值;@Value("#{...}"):表示SpEl表达式通常用来获取Bean的属性;实例/***服务内动态配置**@au......