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日

相关文章

  • Linux的命令行中一些文本操作技巧的实例分享

    下面是详细讲解”Linux的命令行中一些文本操作技巧的实例分享”的完整攻略: 1. 文本操作技巧简介 在Linux的命令行中,我们经常需要对文本进行操作,比如查找、替换、提取等等。这些操作可以通过命令行工具来实现,而不需要使用图形界面的工具。 下面列举一些常用的文本操作技巧: grep:用于在文件中查找指定的文本字符串; sed:用于对指定文件中的文本进行替…

    other 2023年6月26日
    00
  • JavaScript写的一个自定义弹出式对话框代码

    以下是详细讲解 JavaScript 写一个自定义弹出式对话框的完整攻略。 一、简介 弹出式对话框是 Web 开发中常用的组件之一,可用于实现用户输入信息的提示、确认或错误等功能。JavaScript 可以实现一个自定义的弹出式对话框,方便开发者在应用中使用。 二、实现步骤 创建 HTML 结构 首先在 HTML 中创建一个用于弹出式对话框的容器。以下示例使…

    other 2023年6月25日
    00
  • thinkPHP5框架实现基于ajax的分页功能示例

    ThinkPHP5框架实现基于ajax的分页功能示例攻略 1. 示例概述 本示例旨在演示如何使用ThinkPHP5框架实现基于ajax的分页功能。通过ajax异步加载数据和更新页面,实现数据分页展示的效果。整个示例包含以下几个步骤: 创建数据库和表 创建控制器和模型 创建视图文件 编写ajax请求和数据处理逻辑 更新视图展示 接下来,我们将详细介绍每个步骤以…

    other 2023年6月28日
    00
  • 使用Python对MySQL数据操作

    使用Python对MySQL数据操作的完整攻略 1. 安装MySQL驱动程序 在开始之前,我们需要安装Python的MySQL驱动程序。可以使用pip命令来安装,运行以下命令: pip install mysql-connector-python 2. 连接到MySQL数据库 在Python中,我们可以使用mysql.connector模块来连接到MySQL…

    other 2023年8月3日
    00
  • unity中的debug

    Unity中的Debug Debug是指在软件开发中为了找出问题而使用的工具。在Unity中,Debug是一种非常方便的调试工具,用于检查代码中的变量,函数的参数以及代码执行的路径等情况。使用Debug工具,可以帮助我们快速地定位问题,并且提高代码的质量。 Debug的使用 在Unity中,Debug显示的内容会显示在控制台中。要打开控制台,可以按下Ctrl…

    其他 2023年3月28日
    00
  • vue-cli 环境变量 process.env的使用及说明

    vue-cli 环境变量 process.env的使用及说明 在Vue.js项目中,我们可以通过使用process.env来访问环境变量,这在不同的环境下可以用来指定不同的参数或配置。本文将详细讲解如何使用process.env来设置和访问环境变量。 process.env的基本用法 process.env是Node.js中的全局变量,可以用来访问系统环境变…

    other 2023年6月27日
    00
  • Java中抽象类和接口的用法详解

    我们将主要解析Java中抽象类和接口的用法详解。 什么是抽象类和接口? 在Java编程中,抽象类和接口是两个重要的面向对象概念。抽象类和接口都不可以直接实例化,它们只能被继承和实现。它们的主要区别在于使用的场景和变量、方法等的实现方式。 抽象类一般用来表示一个概念上的类,它具有一些通用的方法和属性,但是不能确定具体的实现,即一部分方法没有实现。子类必须实现这…

    other 2023年6月27日
    00
  • 【用户不在sudoers文件中】问题解决

    当用户在 Linux 系统中执行需要管理员权限的命令时,可能会遇到“用户不在sudoers文件中”的错误。这是因为该用户没有被授权执行 sudo 命令的权限。本文将提供两种解决问题的方法,并提供示例说明。 方法一:将用户添加到 sudoers 文件中 sudoers 文件是 Linux 系统中用于授权用户执行 sudo 命令的文件。可以通过编辑该文件,将用户…

    other 2023年5月9日
    00
合作推广
合作推广
分享本页
返回顶部