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 / ...
随机推荐
- JavaScript 单线程之异步编程
Js 单线程之异步编程 先了解一个概念,为什么 JavaScript 采用单线程模式工作,最初设计这门语言的初衷是为了让它运行在浏览器上面.它的目的是为了实现页面的动态交互,而交互的核心是进行 Dom ...
- Springboot目录结构分析
1 src/main/java 存储源码 2 src/main/resource 资源文件夹 (1)src/main/resource/static 用于存放静态资源,如css.js.图片.文件 ...
- Java异常处理最佳实践
总结一些Java异常的处理原则 Java异常处理最佳实践 不要忘记关闭资源 在finally里关闭资源 public void readFile() { FileInputStream fileInp ...
- 随笔总结:8086CPU的栈顶超界问题
我们学习编程都知道栈的超界限问题是非常严重的问题,他可能会覆盖掉其他数据,并且我们不知道这个数据是我们自己保存的用于其他用途的数据还是系统的数据,这样常常容易引发一连串的问题. 在学习汇编的时候,我们 ...
- Spring IOC源码研究笔记(2)——ApplicationContext系列
1. Spring IOC源码研究笔记(2)--ApplicationContext系列 1.1. 继承关系 非web环境下,一般来说常用的就两类ApplicationContext: 配置形式为XM ...
- 配置中心的设计-nacos vs apollo
简介 前面我们分析了携程的 apollo(见 详解apollo的设计与使用),现在再来看看阿里的 nacos. 和 apollo 一样,nacos 也是一款配置中心,同样可以实现配置的集中管理.分环境 ...
- 《Java编程思想》学习笔记_多态
多态 多态指一个行为产生多种状态,针对父类类型可接收其子类类型,最终执行的状态由具体子类确定,其不同子类可呈现出不同状态.例如人[父类]都会跑步[行为],但小孩[子类]跑步.成年人[子类]跑步.运动员 ...
- RocketMQ 集群的搭建部署 以及rocketmq-console-ng仪表台的安装部署
在 RocketMQ 主要的组件如下. NameServerNameServer 集群,Topic 的路由注册中心,为客户端根据 Topic 提供路由服务,从而引导客户端向 Broker 发送消息.N ...
- 从0到1建设智能灰度数据体系:以vivo游戏中心为例
作者: vivo 互联网数据分析团队-Dong Chenwei vivo 互联网大数据团队-Qin Cancan.Zeng Kun 本文介绍了vivo游戏中心在灰度数据分析体系上的实践经验,从&quo ...
- runc hang 导致 Kubernetes 节点 NotReady
Kubernetes 1.19.3 OS: CentOS 7.9.2009 Kernel: 5.4.94-1.el7.elrepo.x86_64 Docker: 20.10.6 先说结论,runc v ...