从一个字段了解Spring

0 阅读4分钟

直切主题.

今天看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类型的类的全限定名.如下: image.png

2,解析配置类

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法功能是解析配置类,首先需要判断给定的beanDefinition是否是配置类.

image.png 为了后续阅读,直接贴出方法体.

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的创建流程.

image.png

到此, 就可以得出一个结论. 针对beanClass字段,Spring没有设计具体的字段类型,而是采用一个字段,两种形态的方式:

在BeanDefinition时,存储String类型的全限定名.

在bean创建前,通过resolveBeanClass加载类,由String类型替换为Class类型.

同时配合volatile字段,保障beanClass字段安全更新.

Object类型配合类型判断方法, 是解析bean定义阶段和创建bean阶段的一个折中.

Spring在正确的时间做正确的事, 在早期解析阶段, 加载类既会造成不必要的错误,还会拖慢容器启动速度,故而将类加载延迟到必要阶段,保证容器正确启动的同时,还能做到加载的类是真正需要的类.

从一个字段的设计,以小见大,可以看出Spring框架在性能,安全,灵活性方面的精细设计.

那么,问题来了, 还有什么设计影响了类的加载? 留个小尾巴,下篇写@Lazy @Conditional注解.