直切主题.
今天看org.springframework.beans.factory.support.AbstractBeanDefinition.class 中beanClass字段.
beanClass中到底是存储Class对象,还是String类型的类名? 为什么没有定义为具体类型,而是采用Object类型?
@Nullable
private volatile Object beanClass;
下面先看beanClass字段的使用场景:
1,构建beanDefinition
在spring中最先使用到这个字段是构建Bean的定义信息时, 也就是通过xml配置或者扫描@Component注解及其衍生注解 生成beanDefition.此时beanClass字段通常存储String类型的类的全限定名.如下:
2,解析配置类
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法功能是解析配置类,首先需要判断给定的beanDefinition是否是配置类.
为了后续阅读,直接贴出方法体.
static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
//获取类名
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
//重点在这段if判断
AnnotationMetadata metadata;
//beanDef是 注解类型的BeanDefinition && 类名相同
if (beanDef instanceof AnnotatedBeanDefinition annotatedBd &&
className.equals(annotatedBd.getMetadata().getClassName())) {
//获取注解元数据
metadata = annotatedBd.getMetadata();
}
//beanDef是 是AbstractBeanDefinition类型 && 类已加载
//abstractBd.hasBeanClass() 在下面展开
else if (beanDef instanceof AbstractBeanDefinition abstractBd && abstractBd.hasBeanClass()) {
Class<?> beanClass = abstractBd.getBeanClass();
//排除以下类型
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
//使用反射方式获取元数据
metadata = AnnotationMetadata.introspect(beanClass);
}
else {
//类尚未加载,使用asm字解码框读取类的注解元数据信息
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
}
//以下就是使用注解元数据信息,判断是否是配置类.不是讨论重点
........
}
从上面的方法,可以得出,spring获取注解元数据, 有两种方式:
反射: 基于jdk反射, 需要类加载到jvm才能被解析
ASM字节码解析: 直接通过ASM提取注解信息(ASM不懂,可以当作黑盒),无需加载类到jvm
如何判断类是否加载到jvm, 也就是通过BeanDefinition类型结合abstractBd.hasBeanClass()返回值来判断. 重点还是在hasBeanClass()方法.
//判定是否指定了Class
public boolean hasBeanClass() {
return (this.beanClass instanceof Class);
}
这也是我们要看beanClass字段的原因 , 类最终都是要加载jvm中的,为什么不直接在此时将类加载到jvm ,反而要通过ASM提取注解元数据?
这就要讲到jvm的类加载机制
加载-> 验证->准备->解析->初始化->使用->卸载.
类加载是一个相对昂贵的操作, 将类加载到jvm中,要验证字节码文件是否合规, 内存分配,符号引用替换 ,类初始化 ,这是一整个流程.而ASM只需要解析类的结构信息,不用执行任何代码,快速且轻量,而在容器启动阶段,ASM更有利于容器快速启动.
同时,在类加载的初始化阶段的主要工作就是: 给类的静态变量赋初始值,执行静态代码块.
在上述容器启动阶段,将类直接加载到jvm, 会触发类中的静态代码块执行, 如果静态代码块中需要的是一些环境信息, 而环境还没有准备好,就会导致容器报错,启动失败.
3,在实际创建bean阶段前
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[]).
在createBean方法中 , 调用resolveBeanClass(mbd,beanName),注意这里的注释,Make sure bean class is actually resolved at this point(确保beanClass在此处被解析), 而resolveBeanClass方法实际就是在完成类加载:
根据BeanDefinition中beanClass字段,beanClass是String类型的全限定名,则根据类全限定名加载类; 如果beanClass是Class对象,则返回clazz,开始bean的创建流程.
到此, 就可以得出一个结论. 针对beanClass字段,Spring没有设计具体的字段类型,而是采用一个字段,两种形态的方式:
在BeanDefinition时,存储String类型的全限定名.
在bean创建前,通过resolveBeanClass加载类,由String类型替换为Class类型.
同时配合volatile字段,保障beanClass字段安全更新.
Object类型配合类型判断方法, 是解析bean定义阶段和创建bean阶段的一个折中.
Spring在正确的时间做正确的事, 在早期解析阶段, 加载类既会造成不必要的错误,还会拖慢容器启动速度,故而将类加载延迟到必要阶段,保证容器正确启动的同时,还能做到加载的类是真正需要的类.
从一个字段的设计,以小见大,可以看出Spring框架在性能,安全,灵活性方面的精细设计.
那么,问题来了, 还有什么设计影响了类的加载? 留个小尾巴,下篇写@Lazy @Conditional注解.