为什么@ConditionalOnClass()注解不会因为其中的Class文件不在路径中而报错

前言

在Spring Boot中有一个注解@ConditionalOnClass(),这个注解意思是仅当其中指明的class文件在classpath中时,配置类才生效。但我们知道如果一个类不在路径上,那么在编译时,带有这个注解的类就无法通过编译。

假设有这样一段代码:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService.class)
public static class SomeServiceConfiguration {
    
    

}

如果这段代码要通过编译,那么至少写这段代码时,classpath中要有SomeService.java这个文件。

编译时

例如如下代码:

public class Test{
    
    
    public static void main(String[] args) {
    
    
         System.out.println("hello!");  
    }
    public void test() {
    
    
         Object obj = Stu.class;
         return;
    }
}

这个文件中引用了Stu类,但是在路径下仅有Test.java文件,并没有Stu.java:

那么运行javac命令将会抛出如下错误:

所以可以推出,那些用了@ConditionalOnClass()配置类在编译时肯定是有它所指明所需的类文件的,只不过打成jar包后剔除了那些类。

运行时

但即便如此,在Spring Boot运行时,如果Spring Boot要通过反射获取到@ConditionalOnClass()中的值,那么还是会报错,我们知道当代码运行到一处需要加载指定类时,如果该类从来未被加载过,那么JVM会去先加载这个类,但是如果这个类的.class文件并不能在classpath中找到,那么就会报错

比如还是刚刚那个例子,这次我们将Stu.class放到路径下,成功编译后,删除Stu.class,运行该文件,输出如下:

发现虽然Test类中的test()方法虽然用到了Stu类,但是由于并不会运行到这一行代码,所以并不会报错。接下来稍微修改一下代码:

public class Test{
    
    
    public static void main(String[] args) {
    
    
         System.out.println("hello!"); 
         new Test().test();
    }
    public void test() {
    
    
         Object obj = Stu.class;
         return;
    }
}

这次我们在main()方法中调用test(),重新编译,并且还是在运行前删除Stu.class文件,输出如下:

发现程序抛出异常,由于运行test()找不到Stu.class文件。

@ConditionalOnClass()的原理

综上所述,那么Spring Boot是怎么做到读取@ConditionalOnClass()注解中的Class对象还不会报错的呢?我们知道读取该注解的值JVM肯定是先要内存中加载该值的Class对象的。其实原理就是Spring Boot并不是通过反射去获取该值,而是直接去读取标注该注解的类的二进制文件,去获取其中的值

这些其实在@ConditionalOnClass()注解的源码中就有相关注释了:

这里意思是说,使用value属性(类型为Class<?>)进行赋值,那么当@ConditionalOnClass()注解作用在@Configuration类上是安全的,因为这时Spring Boot会通过ASM(一个操作字节码的框架)去获取对应的Class值。

而当@ConditionalOnClass()和@Bean搭配使用时,则不能使用value属性赋值了,应该使用name属性(类型为String),大概是因为这个时候Spring Boot不会使用ASM去获取值了。当然除了使用name属性,也可以按注释中给出的代码,继续使用value属性,但是需要多创建一个@Configuration类去隔绝@Conditional条件。

猜你喜欢

转载自blog.csdn.net/weixin_55658418/article/details/130036959
今日推荐