@ConditionalOnClass(A.class)为什么不报错

问题说明

条件加载是springboot自动配置的刚需,其中有两个条件@ConditionalOnClass和@ConditionalOnMissingClass非常特别,它是基于class类是否存在判断的。场景的使用场景如下:

import A;
import B;

@Configuration
class C{
    @Bean
    @ConditionalOnClass(A.class)
    A getBeanA() {
        return new A();
    }
    @Bean
    @ConditionalOnMissingClass("A")
    B getBeanB() {
        return new B();
    }
}

在我们日常开发过程中,如果class A不存在,编译器会提示编译错误。在java运行过程中,当我们遇到class类不存在的时候会报ClassNotFoundException。上面的代码看起来无法成功初始化我们想要的类。但实际情况是spring通过这个机制正确的选择了具体的实现类,这是为什么呢?

JVM ClassLoader理论回顾

java代码的生命周期
在这里插入图片描述
类生命周期
在这里插入图片描述

何时开始类的初始化

Java虚拟机规范中并没有进行强制约束什么情况下需要开始类加载过程。但是对于初始化阶段,虚拟机规范则是严格规定了仅在类被“主动引用”时才会初始化。

主动引用
  • 创建类的实例
  • 访问类的静态变量(除常量【被final修辞的静态变量】)。
  • 访问类的静态方法
  • 反射如(Class.forName(“my.xyz.Test”))
  • 当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
  • 虚拟机启动时,定义了main()方法的那个类先初始化
被动引用
  • 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。对于静态字段,只有直接定义这个字段的类才会被初始化.
  • 通过数组定义来引用类,不会触发类的初始化
  • 访问类的常量,不会初始化类
关于java文件头的import

import的存在纯粹是为了方便写代码,在编译后的class文件中没有import区。在Java源码编译器进行编译时,每个名字都会经过解析(resolution)找到其全名(canonical form)。

ClassNotFoundException

当程序试图使用Class.forname()、Classloader#findSystemClass()、Classloader#loadClass()方法通过字符串名的形式加载此类时,会抛出ClassNotFoundException。

问题分析

从以上理论分析我们可以看出,我们在加载class C时,如果触发没有new A()就不会初始化class A,就不会有ClassNotFoundException。那问题又来了:如果class A不存在,springboot是如何选择执行getBeanA()方法还是getBeanB()方法呢?

springboot Bean加载机制和选择机制

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()将永远不会被调用,默默地在角落里死去。。。

JVM类生命周期概述:加载时机与加载过程: https://blog.csdn.net/justloveyou_/article/details/72466105

`@ConditionalOnClass`注解是Spring Boot提供的一种条件注解,它的作用是根据类路径中是否存在指定的类,来决定是否需要创建某个Bean。如果类路径中存在指定的类,则创建Bean;否则不创建。 在RabbitMQ中,`RabbitTemplate`是一个非常重要的类,它是用于在应用程序中发送和接收消息的核心组件。因此,在使用RabbitMQ时,通常需要创建`RabbitTemplate`实例,并为其配置相关属性。 在Spring Boot中,可以使用`@ConditionalOnClass`注解来判断是否存在`RabbitTemplate`类。如果存在,则说明应用程序中使用了RabbitMQ,此时就可以创建`RabbitTemplate` Bean,并进行相应的配置。如果不存在,则说明应用程序中没有使用RabbitMQ,此时就不需要创建`RabbitTemplate` Bean。 下面是一个示例代码,展示了如何使用`@ConditionalOnClass`注解创建`RabbitTemplate` Bean: ```java @Configuration @ConditionalOnClass(RabbitTemplate.class) public class RabbitConfig { @Autowired private ConnectionFactory connectionFactory; @Bean public RabbitTemplate rabbitTemplate() { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); // TODO: 配置RabbitTemplate return rabbitTemplate; } } ``` 在上述代码中,使用`@ConditionalOnClass(RabbitTemplate.class)`注解来判断是否存在`RabbitTemplate`类。如果存在,则创建`RabbitTemplate` Bean,并通过`rabbitTemplate()`方法进行相应的配置,否则该Bean不会被创建。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值