@ConditionalOnClass和@ConditionalOnMissingClass的神秘探索
疑问为什么打开部分字节码文件(.class)文件,看到部分类报红,找不到该类,难道真的没有该类?那编译的时候不报错吗?Spring在启动的时候为什么会可以正常启动,没有该类还会正常启动,Spring是如何加载和处理这些类的呢?项目启动后,该类会被JVM加载初始化吗?疑点分析这里我随便找了一个类,里面包含一些没有的class,见下图发现字节码文件(RxReactiveStreams.class)不存
疑问
- 为什么打开部分字节码文件(.class)文件,看到部分类报红,找不到该类,难道真的没有该类?那编译的时候不报错吗?
- Spring在启动的时候为什么会可以正常启动,没有该类还能正常启动?Spring是如何加载和处理这些类的呢?
- 项目启动后,该类会被JVM加载初始化吗?
疑点分析
-
这里我随便找了一个类,里面包含一些没有的class,见下图
-
发现字节码文件(RxReactiveStreams.class)不存在,那我在另外一个比较完整的项目,找到该类属于[rxjava-reactive-streams-1.2.1.jar]包下面的
-
这时候我回到本项目,探讨本项目的pom中是否包含此[rxjava-reactive-streams-1.2.1.jar]
借助于IDEA插件MavenHelper在【spring-cloud-openfeign-core-2.2.5.RELEASE.pom】中找到相关引用
- 查 看 引 用 , 里 面 有 s c o p e 、 o p t i o n a l 标 签 的 引 用 , 这 是 我 们 查 看 c l a s s 不 存 在 , 但 是 又 可 以 正 常 编 译 通 过 的 关 键 \color{red}{查看引用,里面有scope、optional标签的引用,这是我们查看class不存在,但是又可以正常编译通过的关键} 查看引用,里面有scope、optional标签的引用,这是我们查看class不存在,但是又可以正常编译通过的关键
简单说明下:
- 这是关键: o p t i o n a l 为 t r u e 的 话 作 用 是 让 新 项 目 依 赖 本 项 目 的 时 候 , 不 去 依 赖 此 j a r 包 , 设 置 f a l s e , 新 项 目 就 会 能 引 用 到 此 j a r 包 \color{red}{optional为true的话作用是让新项目依赖本项目的时候,不去依赖此jar包,设置false,新项目就会能引用到此jar包} optional为true的话作用是让新项目依赖本项目的时候,不去依赖此jar包,设置false,新项目就会能引用到此jar包
- compile,缺省值,适用于所有阶段,会随着项目一起发布。 编译范围依赖在所有的classpath 中可用,同时它们也会被打包
optional为true:这样的话只有直接引用[rxjava-reactive-streams-1.2.1.jar]的jar包才会引到,一些通过间接引用的则不回引用到,这就导致我们看到的时候找不到该class字节码文件;
关于scope、optional详细
- scope的讲解
- optional的讲解
- springboot Bean加载机制和选择机制
原文链接:https://blog.csdn.net/swordcenter/article/details/98937826
springboot Bean加载分为Bean扫描注册和Bean初始化。
- Bean扫描
springboot 通过ConfigurationClassBeanDefinitionReader读取@Configuration注解标识的类。在扫描候选资源时,spring并没有通过Classloader#loadClass()来加载class文件,而是通过Classloader.getResource()获得class二进制文件,通过ClassReader对二进制文件进行ASM语法解析,从而得到候选类和注解的元数据。也就是说在spring扫描需要加载的Bean时,所有候选类都没有加载初始化。
- Bean加载判断
当spring获得候选bean的元信息时,需要判断这个bean是否真的需要被加载。这个就是spring的ConditionEvaluator体系。
比如在AnnotatedBeanDefinitionReader#doRegisterBean()中有判断:
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
在获得ConditionalOnClass注解的元数据时还有个特殊处理,我们声明@ConditionalOnClass(A.class)时,使用的是Class类。为了后续操作时不引起class加载,AnnotatedElementUtils#getMergedAnnotationAttributes(element,annotationName,classValuesAsString,nestedAnnotationsAsMap)将class类转换成了class的名称字符串。
在最终判断class是否存在时,jvm其实还是抛出了ClassNotFoundException,只是异常被吞没了。(~ ̄▽ ̄)~
//OnClassCondition$MatchType.isPresent(String, ClassLoader) line: 219
private static boolean isPresent(String className, ClassLoader classLoader) {
//省略若干行
try {
forName(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;//我就在这里,只是你看不到 O(∩_∩)O
}
}
所以关于这个问题的正确说法应该是,class类不存在时spring条件加载能够正常执行,而不是不报异常。
至此,当Class A不存在时,Bean B被注册到spring容器中,等待初始化。
- Bean初始化
在初始化Bean时,AbstractAutowireCapableBeanFactory#createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)直接通过MethodProxy.invokeSuper()反射调用了C#getBeanB()。C#getBeanA()将永远不会被调用,默默地在角落里死去。。。
问题总结
- 为什么打开部分字节码文件(.class)文件,看到部分类报红,找不到该类,难道真的没有该类?那编译的时候不报错吗?
- 存在该类,只是我们引用jar【B】包的时候,设置optional为true,也就是说【B】只属于直接上司【A】,【C】jar不能调遣【B】jar,【C】jar也不会引用【B】jar包,想要使用需要我们另外手动引用jar包【B】;这就导致我们没有手动引用【B】时,看不到该类;
- 但是其本项目【A】在编译打包的时候,是可以进行编译的,因为【A】是【B】的直属上司
- Spring在启动的时候为什么会可以正常启动,没有该类还能正常启动?Spring是如何加载和处理这些类的呢?
springboot Bean加载机制和选择机制决定了项目可以正常启动,虽然没有该类,但是我们也没有用到该类呀,这就和Spring加载class字节码文件有关,Spring采用的Classloader.getResource()获得class二进制文件,通过ClassReader对二进制文件进行ASM语法解析,从而得到候选类和注解的元数据。也就是说在spring扫描需要加载的Bean时,所有候选类都没有加载初始化。
- 项目启动后,该类会被JVM加载初始化吗?
当spring获得候选bean的元信息时,需要判断这个bean是否真的需要被加载。这个就是spring的ConditionEvaluator体系。
比如在AnnotatedBeanDefinitionReader#doRegisterBean()中有判断
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
在获得ConditionalOnClass注解的元数据时还有个特殊处理,我们声明@ConditionalOnClass(A.class)时,使用的是Class类。为了后续操作时不引起class加载,AnnotatedElementUtils#getMergedAnnotationAttributes(element,annotationName,classValuesAsString,nestedAnnotationsAsMap)将class类转换成了class的名称字符串。
在最终判断class是否存在时,jvm其实还是抛出了ClassNotFoundException,只是异常被吞没了。(~ ̄▽ ̄)~
//OnClassCondition$MatchType.isPresent(String, ClassLoader) line: 219
private static boolean isPresent(String className, ClassLoader classLoader) {
//省略若干行
try {
forName(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;//我就在这里,只是你看不到 O(∩_∩)O
}
}
本文参考链接:https://blog.csdn.net/swordcenter/article/details/98937826

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。
更多推荐
所有评论(0)