大家好,我是“良工说技术”。

今天给大家带来的是springboot中的@ConditionalOnClass注解的用法。上次的@ConditionalOnBean注解还记得吗?

一、@ConditionalOnClass注解初始

看下@CodidtionalOnClass注解的定义,

需要注意的有两点,

  1. 该注解可以用在类及方法上;类指的是标有@Configuration的类,方法是标有@Bean的方法;
  2. 该注解使用了@Conditional注解标记;这是重点

看到这里,有小伙伴会疑惑,讲了那么多@Conditional注解的作用是什么,不急,作用马上来。

@ConditionalOnClass注解的作用是当项目中存在某个类时才会使标有该注解的类或方法生效;

这句话有点拗口,通俗的讲,@ConditionalOnClass标识在@Configuration类上,只有存在@ConditionalOnClass中value/name配置的类该Configuration类才会生效;@ConditionalOnClass标识在@Bean方法上,只有只有存在@ConditionalOnClass中value/name配置的类方法才会生效。看具体的实例更容易理解些

二、@ConditionalOnClass注解用法

从上面@ConditionalOnClass注解的定义中我们知道该注解可以配置两个属性,分别是value和name,其中value和name都是数组,只不过内容不一样,

value是Class的数组,name是全限类名的字符串。

1、使用value属性

开始,我一直使用value属性进行配置,但是总是报错,比如我配置

@Configuration
@ConditionalOnClass(value = {Client.class})
public class MyAutoConfig {
public MyAutoConfig(){
System.out.println("constructor MyAutoConfig");
}
}

该Client是下面的类,

org.springframework.boot.autoconfigure.data.elasticsearch.Client

它是ES中的一个类,我本身配置的含义是只有在Client存在的时候MyAutoConfig才会生效,但是总是不成功。你知道为什么不成功吗?

这是因为我没有引ES的依赖,导致在我的classpath中没有这个类,按照@ConditionalOnClass的理解,应该是不存在则不会生效,但是由于没有这个类,导致的问题是:无法编译,提示下面的错误

java: 找不到符号
符号: 类 Client

这是可以理解的,因为没有这个类,而我要引用这个类肯定是引用不到的,所以编译是失败的,也就程序跑不起来。那么存在一个问题,@ConditionalOnClass注解的value属性要在什么情况下使用?

这里有一个mybatisplus的配置类,

其配置类上标识了@ConditionalOnClass注解,该注解中配置了value属性,且配置了SqlSessionFactory和SqlSessionFactoryBean两个类,

MyBatisPlusAutoConfiguration是在mybatis-plus-boot-starter的jar包下

SqlSessionFactory是在mybatis的jar包下

SqlSessionFactoryBean是在mybaits-spring的jar包下

这三个类分属于不同的jar包,如果我在一个项目中引入了mybatis-plus-boot-starter的jar包,没有引入mybatis的jar包那么MybatisPlusAutoConfiguration不会生效,也就是只有mybatis和mybatis-spring的jar包都引入了,MybatisPlusAutoConfiguration才会生效,才会被纳入spring容器的管理。

需要注意一点:为了防止少引包,在mybatis-plus-boot-starter中会依赖mybatis和mybatis-spring,这也是starter的好处,不会少引包,需要哪些依赖它都引好了。

那么再回到问题的开始,为什么,我配置了一个不存在的类就没成功,那是因为java的源文件需要编译,在编译时会检查类是否存在,不存在肯定是编译不通过的;而如果引用的是jar包中的文件引用另外一个jar的,则是因为jar包经过了编译,已经打包成功了,故不存在问题。

通过value属性需要结合jar包的方式,这里就不演示了,感兴趣的小伙伴可以自己尝试。通过name属性来指定。

2、使用name属性

@ConditionalOnClass注解还有name属性,name属性指定的是全限类名,也就是包含包名+类名。看下我的配置,

@Configuration
@ConditionalOnClass(name = {"com.my.template.config.ClassA"})
public class MyAutoConfig {
public MyAutoConfig(){
System.out.println("constructor MyAutoConfig");
}
}

这里配置了“com.my.template.config.ClassA”,ClassA是我的一个存在的类,

下面启动,看下在启动日志中是否有“constructor MyConfig”打印,

constructor MyAutoConfig
constructor MyAutoConfig2
constructor classA
2022-07-30 17:18:54.113

看到了,日志说明name配置是生效的,也就是存在ClassA则MyAutoConfig会注册到spring的容器中。作为对比,下面配置一个不存在的类ClassD,

@Configuration
@ConditionalOnClass(name = {"com.my.template.config.ClassD"})
public class MyAutoConfig {
public MyAutoConfig(){
System.out.println("constructor MyAutoConfig");
}
}

看下启动日志

constructor MyAutoConfig2
constructor classA
2022-07-30 21:43:30.550 INFO 13116 --- [

从上面的日志可以看到,没有打印出来想要的日志,说明MyAutoConfig没有注册到spring的容器中。

我们知道name属性是一个数组,上面仅仅配置了一个类,如果配置多个会是什么样子,感兴趣的可以自己尝试,这里这直接给出答案,只有name属性中配置的全部满足相应的配置类才会生效。

不知道,你是否对@ConditionalOnClass是怎么实现的感兴趣吗,继续往下看,大揭秘了。

三、@ConditionalOnClass是怎么实现的

要理解@ConditionalOnClass是怎么实现的还是要回到该注解的定义上,前边提到该注解被

@Conditional(OnClassCondition.class)

注解标识,@Conditional注解的含有是要满足条件才会生效,该注解后边再看。今天的主角是OnClassCondition类,看下其继承关系

重点关注XXCondition即可,可以看到最终实现了Condition接口,@Conditional注解的本质就是考查是否满足Codition接口的matches()方法,所以这看SpringBootCondition中matches方法的实现,

@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获得该注解标准的类或方法
String classOrMethodName = getClassOrMethodName(metadata);
try {
//模板方法,该实现在OnClassCodition类中
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
//返回是否符合条件
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
+ ex.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)", ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}

getMatchOutCome()方法使用了模板方法,实现在OnClassCondition类中,这是最要的方法,

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
//获得@ConditionalOnClass注解中配置的value和name属性的值
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
//判断@ConditionOnClass注解配置的类是否都可以加载到,如有加载不到的则放到missing中
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
if (!missing.isEmpty()) {
//有加载不到的,则返回ConditionOutcome对下,其中属性match为false
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));
}
//@ConditionalOnMissingClass的处理逻辑
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
if (!present.isEmpty()) {
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));
}
//返回ConditionOutCome对下,其match属性为true
return ConditionOutcome.match(matchMessage);
}

上面的代码已经给出了注释,对应@ConditionalOnClass注解的处理就是解析器配置的value和name属性,判断配置的类是否加载到,如有未加载到的则直接返回属性match=false的ConditionOutcome对象,那么是如何判断是否加载到的,是通过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) {
//循环调用matches方法
if (classNameFilter.matches(candidate, classLoader)) {
matches.add(candidate);
}
}
return matches;
}

对于@CoditionalOnClass的处理该方法传入的参数为

List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);

那么也就是调用ClassNameFilter.MISSING的matches方法,其方法如下

可以看到调用的是!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;
}
}

具体的实现在resolve方法中,且该方法被try catch包住了,如果加载不到,直接返回false。

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

看到这里,大家明白了,@ConditionalOnClass注解中判断配置的类是否存在使用的方法是Class.forName,类加载。

四、总结

本文主要认识了@ConditionalOnClass注解,分析了其注解的原理,如何判断配置的类是否存在。

  1. @ConditionalOnClass注解有两个属性,分别是value和name,注意其配置方式;
  2. @ConditionalOnClass注解判断配置的类是否存在的方式是通过Class.forName的方式;

推荐阅读

springboot的@ConditionalOnBean注解

深入理解springboot的自动注入

我的第一个springboot  starter

springboot的@ConditionalOnClass注解的更多相关文章

  1. springboot的@ConditionalOnBean注解

      上篇文章中分析了springboot的自动注入的原理,可在文章后面的推荐阅读中温习哦.在自动注入的原理那篇文章中提到了@ConditionalOnXX注解,今天来看下springboot中的@Co ...

  2. springboot整合mybaits注解开发

    springboot整合mybaits注解开发时,返回json或者map对象时,如果一个字段的value为空,需要更改springboot的配置文件 mybatis: configuration: c ...

  3. SpringBoot 中常用注解

    本篇博文将介绍几种SpringBoot 中常用注解 其中,各注解的作用为: @PathVaribale 获取url中的数据 @RequestParam 获取请求参数的值 @GetMapping 组合注 ...

  4. SpringBoot 中常用注解@PathVaribale/@RequestParam/@GetMapping介绍

    SpringBoot 中常用注解@PathVaribale/@RequestParam/@GetMapping介绍 本篇博文将介绍几种如何处理url中的参数的注解@PathVaribale/@Requ ...

  5. springboot整合redis(注解形式)

    springboot整合redis(注解形式) 准备工作 springboot通常整合redis,采用的是RedisTemplate的形式,除了这种形式以外,还有另外一种形式去整合,即采用spring ...

  6. SpringBoot整合Mybatis注解版---update出现org.apache.ibatis.binding.BindingException: Parameter 'XXX' not found. Available parameters are [arg1, arg0, param1, param2]

    SpringBoot整合Mybatis注解版---update时出现的问题 问题描述: 1.sql建表语句 DROP TABLE IF EXISTS `department`; CREATE TABL ...

  7. SpringBoot使用Mybatis注解进行一对多和多对多查询(2)

    SpringBoot使用Mybatis注解进行一对多和多对多查询 GitHub的完整示例项目地址kingboy-springboot-data 一.模拟的业务查询 系统中的用户user都有唯一对应的地 ...

  8. SpringBoot 中常用注解@Controller/@RestController/@RequestMapping的区别

    SpringBoot中常用注解@Controller/@RestController/@RequestMapping的区别 @Controller 处理http请求 @Controller //@Re ...

  9. SpringBoot 中常用注解@Controller/@RestController/@RequestMapping介绍

    原文 SpringBoot 中常用注解 @Controller/@RestController/@RequestMapping介绍 @Controller 处理http请求 @Controller / ...

随机推荐

  1. 项目:Six Sigma

    六西格玛管理(Six Sigma Management)是20世纪80年代末首先在美国摩托罗拉公司发展起来的一种新型管理方式.推行六西格玛管理就是通过设计和监控过程,将可能的失误减少到最低限度,从而使 ...

  2. SmartIDE v0.1.17 已经发布 - 模版库远程模式和插件市场公测

    SmartIDE v0.1.17 已经发布,本次同步更新了CLI (Build 3332) 的稳定版通道和Server (Build 3333) 生产环境(内测中).请参考对应的 安装说明 获取最新版 ...

  3. 「ARC 139F」Many Xor Optimization Problems【线性做法,踩标】

    「ARC 139F」Many Xor Optimization Problems 对于一个长为 \(n\) 的序列 \(a\),我们记 \(f(a)\) 表示从 \(a\) 中选取若干数,可以得到的最 ...

  4. 人体调优不完全指南「GitHub 热点速览 v.22.22」

    本周特推又是一个人体调优项目,换而言之就是如何健康生活,同之前的 HowToLiveLonger研究全因死亡率不同,这个项目更容易在生活中实践,比如,早起晒太阳这么一件"小事"便有 ...

  5. Python3 filter()函数和map()函数

    filter(function or None,iterable) 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换. 该接收两个参数,第 ...

  6. ”只用 1 分钟“ - 超简极速 Apk 签名 & 多渠道打包神器

    众所周知,渠道包作为当下国内 Android 应用市场常见的分发方式,当 APP 和后台交互或进行数据上报时,会带上各自的 channel 渠道信息,以此方便企业 & 开发者统计 APP 在各 ...

  7. 记录一下MySql update会锁定哪些范围的数据

    目录 1.背景 2.前置知识 2.1 数据库的隔离级别 2.2 数据库版本 2.3 数据库的存储引擎 2.4 锁是加在记录上还是索引上 2.5 update...where加锁的基本单位是 2.6 行 ...

  8. 02 java包装类型的缓存机制

    02 java包装类型的缓存机制 Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能. Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,12 ...

  9. 图片放在div中低下会出现一条缝

    页面要达到的样子 中间写的是时候是向div里面放一张图片就行了 <head> <link rel="stylesheet" href="reset.cs ...

  10. IP寻址与规划

    一.IP寻址和子网划分 IP地址的主机部分可被分为三种地址:网络地址.主机地址和定向广播地址. 网络地址是网络号中的第一个地址.它用来将网络内的其他所有网段唯一标识为一个网段或广播域.定向广播地址是网 ...