Spring 源码学习(四) bean 的加载(下)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
日志服务 SLS,月写入数据量 50GB 1个月
简介: 既然我们 Spring 辛辛苦苦将 bean 进行了注册,当然需要拿出来进行使用,在使用之前还需要经过一个步骤,就是 bean 的加载。在第一篇笔记提到了,完成 bean 注册到 beanDefinitionMap 注册表后,还调用了很多后处理器的方法,其中有一个方法 finishBeanFactoryInitialization(),注释上面写着 Instantiateall remaining(non-lazy-init)singletons,意味着非延迟加载的类,将在这一步实例化,完成类的加载。而我们使用到 context.getBean("beanName")方法,如果对应的

创建 bean 的实例

在上面第二个步骤,做的是实例化 bean,然后返回 BeanWrapper

  1. protectedBeanWrapper createBeanInstance(String beanName,RootBeanDefinition mbd,Object[] args){
  2.    // Make sure bean class is actually resolved at this point.
  3.    Class<?> beanClass = resolveBeanClass(mbd, beanName);
  4.    Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
  5.    // Shortcut when re-creating the same bean...
  6.    boolean resolved =false;
  7.    boolean autowireNecessary =false;
  8.    if(args ==null){
  9.        synchronized(mbd.constructorArgumentLock){
  10.            // 如果一个类有多个构造函数,每个构造函数都有不同的参数,调用前需要进行判断对应的构造函数或者工厂方法
  11.            if(mbd.resolvedConstructorOrFactoryMethod !=null){
  12.                resolved =true;
  13.                autowireNecessary = mbd.constructorArgumentsResolved;
  14.            }
  15.        }
  16.    }
  17.    // 如果已经解析过,不需要再次解析
  18.    if(resolved){
  19.        if(autowireNecessary){
  20.            // 实际解析的是 org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor
  21.            // 构造函数自动注入(如果参数有很多个,在匹配构造函数可复杂了,不敢细看=-=)
  22.            return autowireConstructor(beanName, mbd,null,null);
  23.        }
  24.        else{
  25.            // 使用默认的构造函数
  26.            return instantiateBean(beanName, mbd);
  27.        }
  28.    }
  29.    // Candidate constructors for autowiring? 需要根据参数解析构造函数
  30.    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
  31.    if(ctors !=null|| mbd.getResolvedAutowireMode()== AUTOWIRE_CONSTRUCTOR ||
  32.            mbd.hasConstructorArgumentValues()||!ObjectUtils.isEmpty(args)){
  33.        return autowireConstructor(beanName, mbd, ctors, args);
  34.    }
  35.    // Preferred constructors for default construction?
  36.    ctors = mbd.getPreferredConstructors();
  37.    if(ctors !=null){
  38.        // 构造函数注入
  39.        return autowireConstructor(beanName, mbd, ctors,null);
  40.    }
  41.    // No special handling: simply use no-arg constructor. 没有特殊的处理,使用默认构造函数构造
  42.    return instantiateBean(beanName, mbd);
  43. }

大致介绍功能:

  • 如果存在工厂方法则使用工厂方法进行初始化
  • 一个类有多个构造函数,每个构造函数都有不同的参数,所以需要根据参数锁定构造函数进行 bean 的实例化:在这一步我是真心服,为了匹配到特定的构造函数,下了很大的功夫,感兴趣的可以定位到这个函数观看 org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor
  • 如果即不存在工厂方法,也不存在带有参数的构造函数,会使用默认的构造函数进行 bean 的实例化

在这个流程中,通过两种方式,一种是工厂方法,另一种就是构造函数,将传进来的 RootBeanDefinition中的配置二选一生成 bean 实例

具体的不往下跟踪,来看下一个步骤


处理循环依赖

  1. // 是否需要提前曝光,用来解决循环依赖时使用
  2. boolean earlySingletonExposure =(mbd.isSingleton()&&this.allowCircularReferences &&
  3.        isSingletonCurrentlyInCreation(beanName));
  4. if(earlySingletonExposure){
  5.    // 第二个参数是回调接口,实现的功能是将切面动态织入 bean
  6.    addSingletonFactory(beanName,()-> getEarlyBeanReference(beanName, mbd, bean));
  7. }

关键方法是 addSingletonFactory,完成的作用:在 bean 初始化完成前将创建实例的 ObjectFactory 加入单例工厂

一开始就讲过, ObjectFactory 是创建对象时使用的工厂。在对象实例化时,会判断自己依赖的对象是否已经创建好了,判断的依据是查看依赖对象的 ObjectFactory 是否在单例缓存中,如果没有创建将会先创建依赖的对象,然后将 ObjectFactory 放入单例缓存。

这时如果有循环依赖,需要提前对它进行暴露,让依赖方找到并正常实例化。

循环依赖解决方案在下一篇再细讲吧。


属性注入

这也是个高频方法,在初始化的时候要对属性 property 进行注入,贴一些代码片段:

populateBean(beanName, mbd, instanceWrapper);

  1. protectedvoid populateBean(String beanName,RootBeanDefinition mbd,@NullableBeanWrapper bw){
  2.    // 给 awareBeanPostProcessor 后处理器最后一次机会,在属性设置之前修改bean的属性
  3.    boolean continueWithPropertyPopulation =true;
  4.    if(!mbd.isSynthetic()&& hasInstantiationAwareBeanPostProcessors()){
  5.        ...
  6.        if(!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)){
  7.            continueWithPropertyPopulation =false;
  8.            break;
  9.        }
  10.        ...
  11.    }
  12.    PropertyValues pvs =(mbd.hasPropertyValues()? mbd.getPropertyValues():null);
  13.    int resolvedAutowireMode = mbd.getResolvedAutowireMode();
  14.    if(resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE){
  15.        MutablePropertyValues newPvs =newMutablePropertyValues(pvs);
  16.        // Add property values based on autowire by name if applicable.
  17.        if(resolvedAutowireMode == AUTOWIRE_BY_NAME){
  18.            // 根据名字自动注入
  19.            autowireByName(beanName, mbd, bw, newPvs);
  20.        }
  21.        // Add property values based on autowire by type if applicable.
  22.        if(resolvedAutowireMode == AUTOWIRE_BY_TYPE){
  23.            // 根据类型自动注入
  24.            autowireByType(beanName, mbd, bw, newPvs);
  25.        }
  26.        pvs = newPvs;
  27.    }
  28.    // 后处理器已经初始化
  29.    boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
  30.    // 需要依赖检查
  31.    boolean needsDepCheck =(mbd.getDependencyCheck()!=AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
  32.    PropertyDescriptor[] filteredPds =null;
  33.    // 从 beanPostProcessors 对象中提取 BeanPostProcessor 结果集,遍历后处理器
  34.    for(BeanPostProcessor bp : getBeanPostProcessors()){
  35.        ...
  36.    }
  37.    // 在前面也出现过,用来进行依赖检查
  38.    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
  39.    checkDependencies(beanName, mbd, filteredPds, pvs);
  40.    // 将属性应用到 bean 中,使用深拷贝,将子类的属性一并拷贝
  41.    applyPropertyValues(beanName, mbd, bw, pvs);
  42. }

由于代码太长,感兴趣的小伙伴定位到 注释 4.11 位置查看吧

介绍一下处理流程:

  1. 调用 InstantiationAwareBeanPostProcessor 处理器的 postProcessAfterInstantiation 方法,判断控制程序是否继续进行属性填充
  2. 根据注入类型( byName/byType),提取依赖的 bean,统一存入 PropertyValues
  3. 判断是否需要进行 BeanPostProcessor 和 依赖检查:
  • 如果有后处理器,将会应用 InstantiationAwareBeanPostProcessor 处理器的 postProcessProperties 方法,对属性获取完毕填充前,对属性进行再次处理。
  • 使用 checkDependencies 方法来进行依赖检查
  1. 将所有解析到的 PropertyValues 中的属性填充至 BeanWrapper 中。

在这个方法中,根据不同的注入类型进行属性填充,然后调用后处理器进行处理,最终将属性应用到 bean中。

这里也不细说,继续往下走,看下一个方法


初始化 bean

在配置文件中,在使用 <bean> 标签时,使用到了 init-method 属性,这个属性的作用就是在这个地方使用的:bean 实例化前,调用 init-method 指定的方法来根据用户业务进行相应的实例化。来看下入口方法 initializeBean

  1. // 调用初始化方法,例如 init-method
  2. exposedObject = initializeBean(beanName, exposedObject, mbd);

  3. protectedObject initializeBean(finalString beanName,finalObject bean,@NullableRootBeanDefinition mbd){
  4.    // 注释 4.12 securityManage 是啥,不确定=-=
  5.    if(System.getSecurityManager()!=null){
  6.        AccessController.doPrivileged((PrivilegedAction<Object>)()->{
  7.            invokeAwareMethods(beanName, bean);
  8.            returnnull;
  9.        }, getAccessControlContext());
  10.    }
  11.    else{
  12.        // 如果没有 securityManage,方法里面校验了 bean 的类型,需要引用 Aware 接口
  13.        // 对特殊的 bean 处理:Aware/ BeanClassLoader / BeanFactoryAware
  14.        invokeAwareMethods(beanName, bean);
  15.    }
  16.    Object wrappedBean = bean;
  17.    if(mbd ==null||!mbd.isSynthetic()){
  18.        // 熟悉么,后处理器又来了
  19.        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  20.    }
  21.    // 激活用户自定义的 init-method 方法
  22.    invokeInitMethods(beanName, wrappedBean, mbd);
  23.    if(mbd ==null||!mbd.isSynthetic()){
  24.        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  25.    }
  26.    return wrappedBean;
  27. }

这个方法主要是用来进行我们设定的初始化方法的调用,不过在方法内部,还做了其它操作,所以一起来讲下流程:

1、激活 Aware 方法

Spring 中提供了一些 Aware 接口,实现了这个接口的 bean,在被初始化之后,可以取得一些相对应的资源,例如 BeanFactoryAware,在初始化后, Spring 容器将会注入 BeanFactory 的实例。所以如果需要获取这些资源,请引用 Aware 接口。

2、执行后处理器

相信这个大家已经不陌生了,我们可以在诸如 PostProcessor 等后处理器里面自定义,实现修改和扩展。例如 BeanPostProcessor 类中有 postProcessBeforeInitializationpostProcessAfterInitialization,可以对 bean 加载前后进行逻辑扩展,可以将它理解成切面 AOP 的思想。

3、激活自定义的 init 方法

这个方法用途很明显,就是找到用户自定义的构造函数,然后调用它。要注意的是,如果 beanInitializingBean 类型话,需要调用 afterPropertiesSet 方法。

执行顺序是先 afterPropertiesSet,接着才是 init-method 定义的方法。


注册 disposableBean

这是 Spring 提供销毁方法的扩展入口, Spring 爸爸将我们能考虑和想扩展的口子都给预留好。除了通过 destroy-method 属性配置销毁方法外,还可以注册后处理器 DestructionAwareBeanPostProcessor 来统一处理 bean 的销毁方法:

  1. protectedvoid registerDisposableBeanIfNecessary(String beanName,Object bean,RootBeanDefinition mbd){
  2.    AccessControlContext acc =(System.getSecurityManager()!=null? getAccessControlContext():null);
  3.    if(!mbd.isPrototype()&& requiresDestruction(bean, mbd)){
  4.        if(mbd.isSingleton()){
  5.            // 单例模式
  6.            // 注册 DisposableBean
  7.            registerDisposableBean(beanName,
  8.                    newDisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
  9.        }
  10.        else{
  11.            // A bean with a custom scope...
  12.            Scope scope =this.scopes.get(mbd.getScope());
  13.            scope.registerDestructionCallback(beanName,
  14.                    newDisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
  15.        }
  16.    }
  17. }

这里就是往不同的 scope 下, 进行 disposableBean 的注册。


总结

本篇笔记总结了类加载的过程,结合时序图和代码分析,希望对它能有一个更深的了解。

同时对代码编写也有一点感触:

  1. 不要写过长的方法,尽量拆分成小方法,清晰意图

从一开始看 Spring 源码的时候,就惊叹于它代码的整洁和逻辑清晰,入口方法展示也要做的事情,然后工作具体逻辑细分,体现了代码设计者的高超设计,所以在看到有几个方法超过 100 行,心中小小吐槽了一下,看来我跟大佬们写的代码也有共同点,那就是还可以进行优化~

  1. 要在关键地方都打上日志,方便排查和定位

我截取的代码片段,为了篇幅原因,有些逻辑判断和日志处理都给摘掉,但是日志管理是很重要的一环,在关键地方打印日志,在之后排查问题和分析问题会有帮助。不然如果懒得打印日志,在关键的地方没有打印日志,即便出现了问题,也不知道从何查起,导致问题迟迟无法暴露,造成用户的投诉,那就得不偿失了。


由于个人技术有限,如果有理解不到位或者错误的地方,请留下评论,我会根据朋友们的建议进行修正

代码和注释都在里面,小伙伴们可以下载我上传的代码,亲测可运行~

Gitee 地址:https://gitee.com/vip-augus/spring-analysis-note.git

Github 地址:https://github.com/Vip-Augus/spring-analysis-note

参考资料


  1. Spring Core Container 源码分析三:Spring Beans 初始化流程分析


  2. Spring 源码深度解析 / 郝佳编著. -- 北京 : 人民邮电出版社

相关实践学习
日志服务之数据清洗与入湖
本教程介绍如何使用日志服务接入NGINX模拟数据,通过数据加工对数据进行清洗并归档至OSS中进行存储。
相关文章
|
4天前
|
XML Java 数据格式
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
13 1
|
4天前
|
XML druid Java
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
8 0
|
5天前
|
Java Spring
Spring的BeanPostProcessor后置处理器与bean的生命周期
Spring的BeanPostProcessor后置处理器与bean的生命周期
|
3天前
|
消息中间件 安全 Java
学习认识Spring Boot Starter
在SpringBoot项目中,经常能够在pom文件中看到以spring-boot-starter-xx或xx-spring-boot-starter命名的一些依赖。例如:spring-boot-starter-web、spring-boot-starter-security、spring-boot-starter-data-jpa、mybatis-spring-boot-starter等等。
17 4
|
4天前
|
XML 安全 Java
Spring 基础知识学习
Spring 基础知识学习
|
4天前
|
Java Spring 容器
Spring5系列学习文章分享---第六篇(框架新功能系列+整合日志+ @Nullable注解 + JUnit5整合)
Spring5系列学习文章分享---第六篇(框架新功能系列+整合日志+ @Nullable注解 + JUnit5整合)
5 0
|
4天前
|
XML Java 数据库
Spring5系列学习文章分享---第五篇(事务概念+特性+案例+注解声明式事务管理+参数详解 )
Spring5系列学习文章分享---第五篇(事务概念+特性+案例+注解声明式事务管理+参数详解 )
6 0
|
4天前
|
SQL Java 数据库连接
Spring5系列学习文章分享---第四篇(JdbcTemplate+概念配置+增删改查数据+批量操作 )
Spring5系列学习文章分享---第四篇(JdbcTemplate+概念配置+增删改查数据+批量操作 )
6 0
|
4天前
|
XML Java 数据格式
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
7 0
|
4天前
|
Java Spring
Spring注解内容----用来替代Bean
Spring注解内容----用来替代Bean