spring源码学习之bean的初始化以及循环引用

Spring源码学习之bean的初始化以及循环引用

什么是bean

在Spring中,bean是指由Spring IoC容器管理的对象。在使用Spring框架的过程中,我们会将一些Java对象放入Spring容器中,这些对象即成为bean。在Spring容器内部,每个bean以及定义它的bean定义都包含有元数据(meta-data),例如一个bean是单例还是原型,其依赖关系及其他。

bean的生命周期

在 Spring 中,bean 的生命周期包括了几个阶段,如下所示:

  1. 实例化一个 bean;
  2. 如果涉及到实现 BeanNameAware 接口,会调用 setBeanName() 方法,传入该 Bean 的在配置文件中配置的名字。
  3. 如果涉及到实现 BeanFactoryAware 接口,会调用 setBeanFactory() 方法,传入创建它的 BeanFactory。
  4. 如果涉及到实现 ApplicationContextAware 接口,会调用 setApplicationContext() 方法,传入当前的 ApplicationContext 上下文。
  5. 如果实现了 BeanPostProcessor 接口,会调用 postProcessBeforeInitialization() 方法,该方法返回一个对象的实例。 如果目标 bean 是单例的,则在初始化缓存中保存该 bean。
  6. 如果涉及到实现 InitializingBean 接口,会调用 afterPropertiesSet() 方法。
  7. 如果实现了自定义的初始化方法,则调用其自身定义的初始化方法。
  8. 如果实现了 BeanPostProcessor 接口,会调用 postProcessAfterInitialization() 方法,该方法返回一个对象的实例。 如果目标 bean 是单例的,则将更新后的 bean 放回初始化缓存中。
  9. 此 bean 可以被使用了。
  10. 如果该 bean 实现了 DisposableBean 接口,会在容器关闭的时候调用 destory() 方法。
  11. 如果该 bean 实现了自定义的销毁方法,则会调用其自身定义的销毁方法。

bean的初始化

bean的初始化流程可以简单理解为对象的创建和对象内部的一些状态及属性的注入。在Spring容器的初始化过程中,容器会通过反射机制直接调用Java类的构造方法来实例化bean对象。

而对于bean的属性注入,Spring提供了两种方式:

  1. 基于setter方法的自动注入:Spring遍历所有bean的setter方法,然后通过反射将特定的bean注入到setter方法中。这样的注入方式需要先创建bean对象,再通过调用setter方法来给属性赋值。setter方法是用来设置bean对象中属性值的方法,具体的实现定义在Java代码中。
  2. 基于构造方法的自动注入:Spring容器使用反射机制直接调用Java类的构造方法来自动注入bean的属性。这种方式会先实例化对象,然后通过调用Java类的构造方法设置参数,最后将设置好的对象注入到Spring容器中。

对于循环依赖的情况,Spring会采用“三级缓存”的方式来解决问题:

  1. Spring容器在创建bean的时候,首先会在单例缓存(singletonObjects)中查找是否存在给定bean的实例,若有,直接返回实例。若没有,进入第二步。
  2. 在创建实例之前,Spring会将正在创建的bean放入正在创建缓存(earlySingletonObjects)中。之后Spring会创建bean实例并放入其中,若bean的属性需要注入其他bean,则会调用getBean()方法来调用相应的bean,容器会在此过程中递归创建bean。当递归创建bean时,若从单例缓存中检测到已有其实例,将其中的proxy换成singletonObjects中的实例。若此时earlySingletonObjects缓存中出现两个及以上的实例,说明此时发生了循环依赖,则进入第三步。
  3. 此时,若当前bean正在尝试给其他bean注入并且正在获取其他bean的实例时,Spring容器会创建一个代理对象回去。当被依赖的bean创建完成后,再将代理对象替换成真正的实例。

示例说明

示例一

我们来看一个简单的例子来说明bean的初始化以及循环引用问题,如下所示:

public class BeanA {
   private BeanB beanB;

   public BeanB getBeanB() {
       return beanB;
   }

   public void setBeanB(BeanB beanB) {
       this.beanB = beanB;
   }

   public void init() {
       System.out.println("BeanA init");
   }
}

public class BeanB {
   private BeanA beanA;

   public BeanA getBeanA() {
       return beanA;
   }

   public void setBeanA(BeanA beanA) {
       this.beanA = beanA;
   }

   public void init() {
       System.out.println("BeanB init");
   }
}

在这个例子中,BeanABeanB互相依赖,BeanA依赖于BeanBBeanB又依赖于BeanA。为了让这个例子更加生动,我们在BeanABeanBinit方法中给出一些输出。

针对这个例子,我们需要在Spring容器的配置文件中定义两个bean,否则Spring不知道如何创建这两个类的实例:

<bean id="beanA" class="com.xxx.BeanA">
    <property name="beanB" ref="beanB"/>
</bean>

<bean id="beanB" class="com.xxx.BeanB">
    <property name="beanA" ref="beanA"/>
</bean>

如果我们运行代码,我们会发现会报BeanCurrentlyInCreationException的错误,这说明在Spring的创建bean的过程中出现了循环依赖,我们需要采取措施来解决这个问题。

为了解决这个问题,我们可以改变BeanABeanB的初始化方式:改为使用构造函数进行初始化。

public class BeanA {
   private BeanB beanB;

   public BeanA(BeanB beanB) {
       this.beanB = beanB;
   }

   public void init() {
       System.out.println("BeanA init");
   }
}

public class BeanB {
   private BeanA beanA;

   public BeanB(BeanA beanA) {
       this.beanA = beanA;
   }

   public void init() {
       System.out.println("BeanB init");
   }
}

当采用构造函数进行初始化时,我们需要在Spring配置文件中加入以下内容:

<bean id="beanA" class="com.xxx.BeanA">
    <constructor-arg ref="beanB" />
</bean>

<bean id="beanB" class="com.xxx.BeanB">
    <constructor-arg ref="beanA" />
</bean>

现在我们可以运行该程序来实现bean的实例化和初始化了,而且也不再出现循环引用问题。

示例二

我们来看一个更加实际的例子,在实际开发中经常会遇到的问题,通过该示例,我们将进一步说明bean的初始化以及循环引用。

我们假设有两个类:DataSourceTransactionManager。其中,TransactionManager类依赖于DataSource类。

public class DataSource {
   private String url;
   private String username;
   private String password;

   public void setUrl(String url) {
       this.url = url;
   }

   public void setUsername(String username) {
       this.username = username;
   }

   public void setPassword(String password) {
       this.password = password;
   }

   public void init() {
       System.out.println("DataSource init");
   }
}

public class TransactionManager {
   private DataSource dataSource;

   public void setDataSource(DataSource dataSource) {
       this.dataSource = dataSource;
   }

   public void init() {
       System.out.println("TransactionManager init");
   }
}

在Spring容器的配置文件中定义两个bean:

<bean id="dataSource" class="com.xxx.DataSource">
    <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false" />
    <property name="username" value="root" />
    <property name="password" value="root" />
</bean>

<bean id="transactionManager" class="com.xxx.TransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

如果我们运行该程序,我们会在控制台上看到以下输出:

DataSource init
TransactionManager init

以上输出表明,在Spring容器初始化的时候,会先初始化DataSource对象,然后再初始化TransactionManager对象。

总结

本文通过对Spring源码的学习,通过编写实际的代码来说明了bean的初始化及循环引用问题,对于初学者而言,需要理解Spring的bean的生命周期以及基础知识,并清楚地了解Spring容器在其创建过程中处理循环依赖的机制。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:spring源码学习之bean的初始化以及循环引用 - Python技术站

(0)
上一篇 2023年6月20日
下一篇 2023年6月20日

相关文章

  • 应用启动数据初始化接口CommandLineRunner和Application详解

    应用启动数据初始化接口CommandLineRunner和Application是Spring Boot框架中非常重要的特性,它们可以帮助我们在应用程序启动时自动化完成一些初始化的工作,例如初始化数据库连接、加载配置信息等。接下来,我们将详细讲解这两个特性的使用方法以及提供相关示例。 1. CommandLineRunner CommandLineRunne…

    other 2023年6月20日
    00
  • 精简jre1.8

    精简jre1.8的完整攻略 在Java 8中,我们可以使用jlink工具来创建一个自定义的JRE,该JRE只包含需要的块和库,从而减少JRE的大小。以下是详细步骤: 步骤1:安装JDK 8 首先我们需要安JDK 8。可以从Oracle官网下载JDK 8安装程序,并按照安装向导进行安装。 步骤2:创建模化应用程序 我们需要创建一个模块化应用程序,该应用程序将用…

    other 2023年5月7日
    00
  • Java线程和操作系统线程的关系解读

    Java线程和操作系统线程的关系解读 Java语言的线程概念是建立在操作系统线程概念之上的,因此Java线程和操作系统线程之间存在着紧密的联系和依赖关系。 Java线程 Java中线程是由Java虚拟机(JVM)进行管理和调度的。每个Java线程都是由JVM虚拟机中一个线程对象(Thread)来描述的,线程对象需要包含下述属性: 线程状态:Java线程在JV…

    other 2023年6月27日
    00
  • Android开发中匿名设备标识符OAID使用及初始化

    Android开发中匿名设备标识符OAID使用及初始化 简介 随着隐私保护意识的提高,设备标识符的获取变得越来越受到关注。2021年12月1日起,应用商店将禁止在应用中获取IMEI等设备标识符,而是推荐使用集成了匿名设备标识符OAID的SDK。 本文将详细讲解OAID的使用及初始化方法。 OAID的获取 1. 集成SDK 由于OAID是从Android Q(…

    other 2023年6月20日
    00
  • 安装office2010后word新建docx文档的方法

    安装Office 2010后Word新建docx文档的方法攻略 以下是安装Office 2010后使用Word新建docx文档的详细步骤: 打开Microsoft Word:在Windows操作系统中,点击开始菜单,找到Microsoft Office文件夹,然后点击Microsoft Word图标来打开Word应用程序。 创建新文档:在Word应用程序中,…

    other 2023年8月6日
    00
  • java学习技术分享:java中的原子操作

    Java学习技术分享:Java中的原子操作 在Java中,原子操作是指不可被中断的操作,即使在多线程环境也能保证操作的原性。本文将详细介绍Java中的原子操作,包括两个示例说明。 1. 原子的概念 原子是指不可被中断的操作,即使在多线程环境下也能保证操作的原子性。在Java中,子操作通常用于多线程环境下的共享变量,以避免数据竞争和线程安全问题。 Java中提…

    other 2023年5月9日
    00
  • ubuntu更换科大源

    当Ubuntu系统的软件源下载速度较慢或更新效率较低时,可以更换为科大源,以提高软件下载速度和更新效率。以下是更换Ubuntu系统软件源为科大源的完整攻略: 步骤一:备份原有软件源 在更换软件源之前,需要备份原有软件源,以便在更换后出现问题时可以恢复原有软件源。以下是备份软件源的命令: sudo cp /etc/apt/sources.list /etc/a…

    other 2023年5月9日
    00
  • 测试webservice接口工具

    测试webservice接口工具 在开发Web应用程序的过程中,我们经常需要使用WebService接口来实现与其他系统的数据交互,而在开发和测试阶段,我们需要使用一些工具来测试这些接口是否可靠。在本文中,我将介绍几个常用的测试WebService接口的工具。 SoapUI SoapUI是一个功能强大的开源工具,可以用于创建和测试Web服务。它支持多种协议(…

    其他 2023年3月28日
    00
合作推广
合作推广
分享本页
返回顶部