springboot的@ConditionalOnClass注解
大家好,我是“良工说技术”。
今天给大家带来的是springboot中的@ConditionalOnClass注解的用法。上次的@ConditionalOnBean注解还记得吗?
一、@ConditionalOnClass注解初始
看下@CodidtionalOnClass注解的定义,
需要注意的有两点,
- 该注解可以用在类及方法上;类指的是标有@Configuration的类,方法是标有@Bean的方法;
- 该注解使用了@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注解,分析了其注解的原理,如何判断配置的类是否存在。
- @ConditionalOnClass注解有两个属性,分别是value和name,注意其配置方式;
- @ConditionalOnClass注解判断配置的类是否存在的方式是通过Class.forName的方式;
推荐阅读
springboot的@ConditionalOnBean注解
springboot的@ConditionalOnClass注解的更多相关文章
- springboot的@ConditionalOnBean注解
上篇文章中分析了springboot的自动注入的原理,可在文章后面的推荐阅读中温习哦.在自动注入的原理那篇文章中提到了@ConditionalOnXX注解,今天来看下springboot中的@Co ...
- springboot整合mybaits注解开发
springboot整合mybaits注解开发时,返回json或者map对象时,如果一个字段的value为空,需要更改springboot的配置文件 mybatis: configuration: c ...
- SpringBoot 中常用注解
本篇博文将介绍几种SpringBoot 中常用注解 其中,各注解的作用为: @PathVaribale 获取url中的数据 @RequestParam 获取请求参数的值 @GetMapping 组合注 ...
- SpringBoot 中常用注解@PathVaribale/@RequestParam/@GetMapping介绍
SpringBoot 中常用注解@PathVaribale/@RequestParam/@GetMapping介绍 本篇博文将介绍几种如何处理url中的参数的注解@PathVaribale/@Requ ...
- springboot整合redis(注解形式)
springboot整合redis(注解形式) 准备工作 springboot通常整合redis,采用的是RedisTemplate的形式,除了这种形式以外,还有另外一种形式去整合,即采用spring ...
- 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 ...
- SpringBoot使用Mybatis注解进行一对多和多对多查询(2)
SpringBoot使用Mybatis注解进行一对多和多对多查询 GitHub的完整示例项目地址kingboy-springboot-data 一.模拟的业务查询 系统中的用户user都有唯一对应的地 ...
- SpringBoot 中常用注解@Controller/@RestController/@RequestMapping的区别
SpringBoot中常用注解@Controller/@RestController/@RequestMapping的区别 @Controller 处理http请求 @Controller //@Re ...
- SpringBoot 中常用注解@Controller/@RestController/@RequestMapping介绍
原文 SpringBoot 中常用注解 @Controller/@RestController/@RequestMapping介绍 @Controller 处理http请求 @Controller / ...
随机推荐
- spring boot redis 写入异常
redis 的 key value 使用 json 序列化.反序列化时,写入的 bean 不能是 final 类型的类,否则无法解析
- 国内访问 git 慢的方法
在国内访问 git 的时候,总会存在访问慢或者git clone 的时候报下面的错误 这个时候,我们可以使用代理的方式去进行访问 需要注意的是:你必须存在一个国外的,能够让你快速访问到 GitHub ...
- 如何定制.NET6.0的日志记录
在本章中,也就是整个系列的第一部分将介绍如何定制日志记录.默认日志记录仅写入控制台或调试窗口,这在大多数情况下都很好,但有时需要写入到文件或数据库,或者,您可能希望扩展日志记录的其他信息.在这些情况下 ...
- 112_Power Pivot 销售订单按 sku 订单类型特殊分类及占比相关
博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 经过了一个双十一后,天天面对的都是订单.于是有了关于销售订单按sku类型分类的需求. 说明:(暂且不讨论这样分类 ...
- 使用多线程提高REST服务器性能
异步处理REST服务 1.使用Runnable异步处理Rest服务 释放主线程,启用副线程进行处理,副线程处理完成后直接返回请求 主要代码 import java.util.concurrent.Ca ...
- 历经70+场面试,我发现了大厂面试的bug,并总结其中心得
想起了学弟在去年秋招时面试了50余家,加上暑期实习面试了20余家,加起来也面试了70余场. 基本把国内有名的互联网公司都面了一遍,不敢说自己的面试经验很丰富,但也是不差的. 这次专门把大厂的面试做了个 ...
- 我用 AntV/S2 买了一套房
背景 经过一年多的摇号,我在前两天收到了某网红盘的摇中通知.还沉浸在摇中房屋喜悦中的我,很快被售房顾问告知选房的人很多,每位购房者的选房时间都很短,必须 一分钟内 快速选房.并且,排在 400 多号的 ...
- 入坑KeePass(二)重置keepass设置
保留好.kdbx和密钥文件,软件的文件可以删除掉,重新下载并解压设置就恢复默认了
- 为什么要写blog????
写 blog 文章,是种与自我的对话,也是种与外界的联系,也是获得 level up 或 skill learned 的契机. 借口:我不太会写文章,不太会表达,没有东西好写,没人会看我的文章 你想让 ...
- 开发工具-SSMS官方下载地址
更新记录 2022年6月10日 完善标题. SQL Server Management Studio官方下载地址 https://docs.microsoft.com/en-us/sql/ssms/d ...