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

yizhihongxing

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日

相关文章

  • 基于hal库的stm32的dsp库详解(附fft应用)

    基于HAL库的STM32的DSP库详解(附FFT应用) DSP库简介 DSP库是STM32 HAL库的一个扩展,它提供了一组用于数字信号处理的函数。这些函数可以用于实现各种数字信号处理算法,例如滤波、FFT、卷积等。DSP库支持多种数据类型,包括整数、浮点数和Q格式数据。在本文中,我们将重点介绍DSP库中的FFT函数,并提供两个示例说明。 FFT函数 FFT…

    other 2023年5月8日
    00
  • @Autowired注解在抽象类中失效的原因及解决

    自动装配(autowiring)是Spring框架提供的一种便捷的方式,可以自动将相互依赖的组件(bean)注入到Java类中。@Autowired注解可以实现自动注入,但是在抽象类中有时会失效。下面是@Autowired注解在抽象类中失效的原因及解决方案的完整攻略。 原因 @Autowire注解功能实现的原理是Spring容器在启动时,扫描所有使用@Com…

    other 2023年6月26日
    00
  • ntrun怎么使用?nTrun快速启动工具使用技巧分享

    ntrun怎么使用? 1. ntrun是什么? ntrun是一款快速启动工具,可以帮助用户快速启动Windows系统中的各种程序和命令。使用ntrun可以提高用户的工作效率,特别是经常需要使用命令行工具的用户。 2. 如何使用ntrun? 2.1 下载并安装ntrun ntrun可以在官方网站上下载。下载完成后,按照提示进行安装即可。 2.2 启动ntrun…

    other 2023年6月27日
    00
  • jQuery 判断图片是否加载完成方法汇总

    jQuery 判断图片是否加载完成方法汇总 为什么要判断图片是否加载完成 在网页中,我们经常会用到图片,对于图片的加载,我们也需要及时获取到,才能进行一些后续操作,比如图片的轮播、图片的放大缩小等操作。但在实际中图片的加载是一个异步的过程,我们并不能很好地控制它的加载速度,所以就需要判断图片是否已经加载完成,才能进行后续的操作。 方式一:使用load事件 $…

    other 2023年6月25日
    00
  • ASP.NET 控件开发系列之图片切换web控件

    当开发ASP.NET网站时,我们常常需要实现图片切换效果。这时,我们可以使用ASP.NET控件开发系列之图片切换Web控件来方便地实现图片切换功能。下面是该控件的完整攻略: 控件的基本结构 控件的基类为 System.Web.UI.WebControls.WebControl,可以通过继承该类来创建自定义控件。 控件需要实现 System.Web.UI.IP…

    other 2023年6月26日
    00
  • PyQt5 多窗口连接实例

    下面就给您详细讲解一下“PyQt5 多窗口连接实例”的完整攻略。 简介 在 PyQt5 中,我们可以很容易地实现多窗口连接的效果。通常来说,我们需要将每个窗口作为一个类来实现,并且使用信号和槽来实现它们之间的通信。在本文中,将会实现一个包含多个窗口的小应用程序,通过它,您可以了解到如何实现多窗口连接。 步骤 步骤1 创建主窗口 首先,我们需要创建一个主窗口。…

    other 2023年6月27日
    00
  • 通过spring注解开发,简单测试单例和多例区别

    当然!下面是关于\”通过Spring注解开发,简单测试单例和多例区别\”的完整攻略,包含两个示例说明。 … … … … … … … … … … … … … … … … … … … … … … … … … … …

    other 2023年8月20日
    00
  • c#-log4net没有输出

    以下是关于“c#-log4net没有输出”的完整攻略,包括原因分析、解决方法和两个示例。 原因分析 c#-log4net没有输出的原因可能有以下几: 配置文件错误:log4net需要正确的配置文件才能正常工作。如果配置文件有误,可能会导致log4net没有输出。 日志级别设置错误:如果日志级别设置过高,可能会导致log4net没有输出。 日志输出目标设置:如…

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