摘要:在利用 Spring 进行 IOC 配置时,关于 bean 的配置和使用一直都是比较重要的一部分,同时如何合理的使用和创建 bean 对象,也是小伙伴们在学习和使用 Spring 时需要注意的部分,所以这一篇文章我就来和大家讲一下有关 Spring 中 bean 的作用域和其生命周期。

本文分享自华为云社区《详解Spring中Bean的作用域与生命周期》,原文作者:灰小猿。

 

在利用 Spring 进行 IOC 配置时,关于 bean 的配置和使用一直都是比较重要的一部分,同时如何合理的使用和创建 bean 对象,也是小伙伴们在学习和使用 Spring 时需要注意的部分,所以这一篇文章我就来和大家讲一下有关 Spring 中 bean 的作用域和其生命周期。

一、Bean 的作用域

首先我们来讲一下有关于 bean 的作用域

 

一般情况下,我们书写在 IOC 容器中的配置信息,会在我们的 IOC 容器运行时被创建,这就导致我们通过 IOC 容器获取到 bean 对象的时候,往往都是获取到了单实例的 Bean 对象

 

这样就意味着无论我们使用多少个 getBean()方法,获取到的同一个 JavaBean 都是同一个对象,这就是单实例 Bean,整个项目都会共享这一个 bean 对象。

 

在 Spring 中,可以在元素的 scope 属性里设置 bean 的作用域,以决定这个 bean 是单实例的还是多实例的。Scope 属性有四个参数,具体的使用可以看下图:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

1、单实例 Bean 声明

默认情况下,Spring 只为每个在 IOC 容器里声明的 bean 创建唯一一个实例,整个 IOC 容器范围内都能共享该实例:所有后续的 getBean()调用和 bean 引用都将返回这个唯一的 bean 实例。该作用域被称为 singleton,它是所有 bean 的默认作用域。也就是单实例。

 

为了验证这一说法,我们在 IOC 中创建一个单实例的 bean,并且获取该 bean 对象进行对比:

<!-- singleton单实例bean
  1、在容器创建时被创建
  2、只有一个实例
  -->
<bean   scope="singleton"></bean>

测试获取到的单实例 bean 是否是同一个:

@Test
public void test09() {
    // 单实例创建时创建的两个bean相等
    Book book03 = (Book)iocContext3.getBean("book02");
    Book book04 = (Book)iocContext3.getBean("book02");
    System.out.println(book03==book04);
}

得到的结果是 true

2、多实例 Bean 声明

而既然存在单实例,那么就一定存在多实例。我们可以为 bean 对象的 scope 属性设置 prototype 参数,以表示该实例是多实例的,同时获取 IOC 容器中的多实例 bean,再将获取到的多实例 bean 进行对比

<!-- prototype多实例bean
1、在容器创建时不会被创建,
2、只有在被调用的时候才会被创建
3、可以存在多个实例
 -->
<bean   scope="prototype"></bean>

测试获取到的多实例 bean 是否是同一个:

@Test
public void test09() {
    // 多实例创建时,创建的两个bean对象不相等
    Book book01 = (Book)iocContext3.getBean("book01");
    Book book02 = (Book)iocContext3.getBean("book01");
    System.out.println(book01==book02);
}

得到的结果是 false

 

这就说明了,通过多实例创建的 bean 对象是各不相同的。

 

在这里需要注意

 

同时关于单实例和多实例 bean 的创建也有不同,当 bean 的作用域为单例时,Spring 会在 IOC 容器对象创建时就创建 bean 的对象实例。而当 bean 的作用域为 prototype 时,IOC 容器在获取 bean 的实例时创建 bean 的实例对象

二、Bean 的生命周期

1、bean 的初始和销毁

其实我们在 IOC 中创建的每一个 bean 对象都是有其特定的生命周期的,在 Spring 的 IOC 容器中可以管理 bean 的生命周期,Spring 允许在 bean 生命周期内特定的时间点执行指定的任务。如在 bean 初始化时执行的方法和 bean 被销毁时执行的方法。

 

Spring IOC 容器对 bean 的生命周期进行管理的过程可以分为六步

 

1. 通过构造器或工厂方法创建 bean 实例

2. 为 bean 的属性设置值和对其他 bean 的引用

3. 调用 bean 的初始化方法

4. bean 可以正常使用

5. 当容器关闭时,调用 bean 的销毁方法

 

那么关于 bean 的初始和销毁时执行的方法又该如何声明呢?

 

首先我们应该在 bean 类内部添加初始和销毁时执行的方法。如下面这个 javabean:

package com.spring.beans;


public class Book {
	private String bookName;
	private String author;
	/**
	 * 初始化方法
	 * */
	public void myInit() {
		System.out.println("book bean被创建");
	}
	
	/**
	 * 销毁时方法
	 * */
	public void myDestory() {
		System.out.println("book bean被销毁");
	}
	
	public String getBookName() {
		return bookName;
	}
	public void setBookName(String bookName) {
		this.bookName = bookName;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	@Override
	public String toString() {
		return "Book [bookName=" + bookName + ", author=" + author + "]";
	}
}

这时我们在配置 bean 时,可以通过 init-method 和 destroy-method 属性为 bean 指定初始化和销毁方法

<!-- 设置bean的生命周期
destory-method:结束调用的方法
init-method:起始时调用的方法
 -->
<bean   destroy-method="myDestory" init-method="myInit"></bean>

这样当我们在通过 IOC 容器创建和销毁 bean 对象时就会执行相应的方法

 

但是这里还是有一点需要注意

 

我们上面说了,单实例的 bean 和多实例的 bean 的创建时间是不同的,那么他们的初始方法和销毁方法的执行时间就稍稍有不同。

 

单实例下 bean 的生命周期

容器启动——>初始化方法——>(容器关闭)销毁方法

 

多实例下 bean 的生命周期

容器启动——>调用 bean——>初始化方法——>容器关闭(销毁方法不执行)

2、bean 的后置处理器

什么是 bean 的后置处理器?bean 后置处理器允许在调用初始化方法前后对 bean 进行额外的处理

 

bean 后置处理器对 IOC 容器里的所有 bean 实例逐一处理,而非单一实例

 

其典型应用是:检查 bean 属性的正确性或根据特定的标准更改 bean 的属性。

 

bean 后置处理器使用时需要实现接口:

org.springframework.beans.factory.config.BeanPostProcessor。

 

在初始化方法被调用前后,Spring 将把每个 bean 实例分别传递给上述接口的以下两个方法:

postProcessBeforeInitialization(Object, String)调用前

postProcessAfterInitialization(Object, String)调用后

 

如下是一个实现在该接口的后置处理器:

package com.spring.beans;


import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;


/**
 * 测试bean的后置处理器
 * 在这里要注意一点是为了出现bean和beanName,而不是arg0、arg1,需要绑定相应的源码jar包
 * */
public class MyBeanPostProcessor implements BeanPostProcessor{


	/**
	 * postProcessBeforeInitialization
	 * 初始化方法执行前执行
	 * Object bean
	 * String beanName xml容器中定义的bean名称
	 * */
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		// TODO Auto-generated method stub
		System.out.println("【"+ beanName+"】初始化方法执行前...");
		return bean;
	}


	/**
	 * postProcessAfterInitialization
	 * 初始化方法执行后执行
	 * Object bean
	 * String beanName xml容器中定义的bean名称
	 * */
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		// TODO Auto-generated method stub
		System.out.println("【"+ beanName+"】初始化方法执行后...");
		return bean;
	}


}

将该后置处理器加入到 IOC 容器中:

<!-- 测试bean的后置处理器 -->
<bean  ></bean>

由于现在我们的 bean 对象是单实例的,所以容器运行时就会直接创建 bean 对象,同时也会执行该 bean 的后置处理器方法和初始化方法,在容器被销毁时又会执行销毁方法。我们测试如下:

//*************************bean生命周期*****************
//	由于ApplicationContext是一个顶层接口,里面没有销毁方法close,所以需要使用它的子接口进行接收
	ConfigurableApplicationContext iocContext01 = new ClassPathXmlApplicationContext("ioc1.xml");
	
	@Test
	public void test01() {
		iocContext01.getBean("book01");
		iocContext01.close();
	}

运行结果:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

总结一下后置处理器的执行过程

 

1. 通过构造器或工厂方法创建 bean 实例

2. 为 bean 的属性设置值和对其他 bean 的引用

3. 将 bean 实例传递给 bean 后置处理器的**
postProcessBeforeInitialization()**方法

4. 调用 bean 的初始化方法

5. 将 bean 实例传递给 bean 后置处理器的**
postProcessAfterInitialization()**方法

6. bean 可以使用了

7. 当容器关闭时调用 bean 的销毁方法

 

所以添加 bean 后置处理器后 bean 的生命周期为

容器启动——后置处理器的 before...——>初始化方法——>后置处理器的 after...———>(容器关闭)销毁方法

 

点击关注,第一时间了解华为云新鲜技术~