在阅读spring-boot源码中 关于自动装配这一块的信息我们经常会在 XXXAutoConfiguration类中发现一些class缺失没有如:RedisAutoConfiguration举例
这样的缺少class如何成功打包 是如何成功编译的?
我们可以在github中找到spring-boot项目中的autoconfigure项目
在这个文件中 spring-boot/spring-boot-project/spring-boot-autoconfigure/build.gradle
由于springboot采用的gradle 进行管理大致功能与 maven类似
optional("org.springframework.data:spring-data-jdbc")
optional("org.springframework.data:spring-data-ldap")
optional("org.springframework.data:spring-data-mongodb")
optional("org.springframework.data:spring-data-neo4j")
optional("org.springframework.data:spring-data-r2dbc")
optional("org.springframework.data:spring-data-redis")
optional("org.springframework.hateoas:spring-hateoas")
optional("org.springframework.security:spring-security-acl")
optional("org.springframework.security:spring-security-config")
optional("org.springframework.security:spring-security-data") {
exclude group: "javax.xml.bind", module: "jaxb-api"
}
optional("org.springframework.security:spring-security-messaging")
optional("org.springframework.security:spring-security-oauth2-client")
optional("org.springframework.security:spring-security-oauth2-jose")
optional("org.springframework.security:spring-security-oauth2-resource-server")
optional("org.springframework.security:spring-security-rsocket")
optional("org.springframework.security:spring-security-saml2-service-provider")
optional("org.springframework.security:spring-security-web")
我们可以看到这个项目引入很多依赖但我我们在引用boot的时候为什么没有自动引入这些的?
原因就在于optional可选依赖
maven的话就是
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
当你使用这个时依赖就不会显式的进行引用
至于为什么可以成功编译 打jar我就不用多说了吧 (人家项目已经依赖的已经)
那么的话又会引发一个问题当我们new这个RedisAutoConfiguration对象时会不会抱错呢?
RedisAutoConfiguration redisAutoConfiguration = new RedisAutoConfiguration();
可以正常实例化
当我们再调用实例化后的redisTemplate的这个方法呢?
RedisAutoConfiguration redisAutoConfiguration = new RedisAutoConfiguration();
redisAutoConfiguration.redisTemplate(null);
哦 NO编译失败了
/Users/ONEX/programming/code/test-code/src/main/java/com/onex/annon/AnnTest.java:25:45
java: 无法访问org.springframework.data.redis.core.RedisTemplate
找不到org.springframework.data.redis.core.RedisTemplate的类文件
为什么可以正常实例化但是调用此类的方法时就会引发起编译失败呢?
实例化后在Java内存中RedisAutoConfiguration.class到底长什么样子?
我们可以通过 jdk8中的/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
进行查看
你会发现有些import,注解没有了 具体为什么会没有我也不知道🤷♂️反正通过
RedisAutoConfiguration.class 获取类上面的注解是可以正常获取到的
那么就回到上面那个问题(为什么可以正常实例化但是调用此类的方法时就会引发起编译失败呢?)
Java在编译期会将你使用到的类进行编译
可以写一个 Demo
package com.onex.demo;
/**
* @author buhuazhen
* @date 2022/6/21
* @email 3038525872@qq.com
*/
public class StaticDemo {
static {
System.out.println("StaticDemo static");
}
}
package com.onex.demo;
import com.onex.demo.StaticDemo;
/**
* @author buhuazhen
* @date 2022/6/21
* @email 3038525872@qq.com
*/
public class Main {
public static void main(String[] args) {
System.out.println("gogogo");
}
}
结果:
gogogo
static {} 运行在类的首次加载中
但是启动Main类时可以清楚的看出来没有去加载 StaticDemo 所以StaticDemo将不会去校验是否会存在
清楚了这点之后我们对于Spring-autoconfigure 显示class找不到却可以正常启动就有了一个理解了
那么回到文章的问题 @ConditionalOnClass(RedisOperations.class) spring是如何进行校验class是否存在的呢
有的朋友将会想了 这不简单 通过 .class 获取注解信息不接可以了吗
RedisAutoConfiguration redisAutoConfiguration = new RedisAutoConfiguration();
ConditionalOnClass annotation = redisAutoConfiguration.getClass().getAnnotation(ConditionalOnClass.class);
Class<?>[] value = annotation.value();
for (Class< ? > v : value) {
System.out.println("存不存在呢\t"+v);
}
结果:
Exception in thread "main" java.lang.TypeNotPresentException: Type org.springframework.data.redis.core.RedisOperations not present
Caused by: java.lang.ClassNotFoundException: org.springframework.data.redis.core.RedisOperations
报class找不到....
当然了 校验一个class对象是否存在 class对象 这不很自相矛盾吗
:那么通过判断会不会报错不就可以判断出来了 class会不会存在吗
你这个小机灵鬼 可以是可以的但spring 没有这么做
spring源码中没有这么做可能是为了 与name()公用同一套的逻辑吧
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* The classes that must be present. Since this annotation is parsed by loading class
* bytecode, it is safe to specify classes here that may ultimately not be on the
* classpath, only if this annotation is directly on the affected component and
* <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
* use this annotation as a meta-annotation, only use the {@link #name} attribute.
* @return the classes that must be present
*/
Class<?>[] value() default {};
/**
* The classes names that must be present.
* @return the class names that must be present.
*/
String[] name() default {};
}
那么我们如何获取这个不存在的 class对象呢?
很抱歉通过Java自带的一些属性无法获取到
只能通过 ASM Java字节码技术进行获取 关于ASM大家可以自己去了解我这里不过多介绍
Spring对ASM自己实现了 提供了一个类
org.springframework.core.type.classreading.SimpleMetadataReaderFactory 这个类
废话不多说直接上代码
package com.onex.demo;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import java.util.Map;
/**
* @author buhuazhen
* @date 2022/6/21
* @email 3038525872@qq.com
*/
public class Main {
public static void main(String[] args) throws Exception {
RedisAutoConfiguration redisAutoConfiguration = new RedisAutoConfiguration();
SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory(Thread.currentThread().getContextClassLoader());
MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader(RedisAutoConfiguration.class.getName());
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 可以获取到 ConditionalOnClass 以方法名:值 的map集合
Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(ConditionalOnClass.class.getName(), true);
// 怎么能获取到一个不存在class的class对象中 所以这里返回的是class的全限定类名
String[] classNames = (String[]) annotationAttributes.get("value");
// 打印出来
for (String className : classNames) {
System.out.println(className);
}
// 有了 全限定类名再判断有没有class 不接很简单了名
for (String className : classNames) {
try{
Class.forName(className);
System.out.println("存在"+className);
}catch (Exception e){
System.out.println("不存在"+className);
}
}
}
}
Spring 的源码中也是通 Class.forName(className) 进行判断的哦
org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition
static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
resolve(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;
}
}
至此spring 是如何判断class是否存在的一个简单Demo也就完成了