Spring Bean的作用域及生命周期

一、作用域(scope)和生命周期

作用域

作用域限定了 Bean 的作用范围。在 Spring 配置文件定义 Bean 时,通过声明 scope 配置项,可以灵活定义 Bean 的作用范围。例如,当希望每次 IOC 容器返回的 Bean 是同一个实例时,可以设置 scope 为 singleton;当希望每次 IOC 容器返回的 Bean 实例是一个新的实例时,可以设置 scope 为 prototype。scope 配置项有 5 个属性,用于描述不同的作用域:singletonprototype、request、session、global-session。说明如下:

  • singleton:唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype:每次请求都会创建一个新的 bean 实例。
  • request:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

生命周期

Bean 的生命周期只有四个阶段(实例化 -> 属性赋值 -> 初始化 -> 销毁)。实例化和属性赋值对应构造方法和 setter 方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

主要逻辑都在 doCreate() 中,逻辑很清晰,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应。
1️⃣createBeanInstance() -> 实例化
2️⃣populateBean() -> 属性赋值
3️⃣initializeBean() -> 初始化
查看源码能证明实例化、属性赋值和初始化这三个生命周期的存在。至于销毁,是在容器关闭时调用的,详见ConfigurableApplicationContext#close()。

二、singleton(单例)

  1. 默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。
  2. 在创建对象的时候(先调用构造器),会去调用init-method=".."属性值中所指定的方法。
  3. 对象在被销毁的时候,会调用destroy-method="..."属性值中所指定的方法(例如调用 container.destroy() 的时候)。
  4. lazy-init="true",可以让这个对象在第一次被访问的时候创建,而不是被加载的时候。

此外,singleton 类型的 bean 定义从容器启动到第一次被请求而实例化开始,只要容器不销毁或退出,该类型的 bean 的单一实例就会一直存活。典型单例模式,如同 servlet 在 web 容器中的生命周期。

三、prototype(原型)

  1. Spring 读取 xml 文件的时候,不会创建对象。
  2. 在每一次访问这个对象的时候,Spring 容器都会创建这个对象,并且调用init-method=".."属性值中所指定的方法。
  3. 对象销毁的时候,Spring 容器不会调用任何方法。因为不是单例,这个类型的对象有很多个,Spring 容器一旦把这个对象交给请求方之后,就不再管理这个对象了。

理解:

如同分苹果,将苹果的 bean 的 scope 属性声明为 prototype。在每个人领取苹果的时候,都是发一个新的苹果给他,发完之后,别人爱怎么吃就怎么吃,爱什么时候吃什么时候吃,但是注意吃完要把苹果核扔到垃圾箱!对于那些不能共享使用的对象类型,应该将其定义的 scope 设为 prototype。

最典型的体现就是 Spring 与 struts2 进行整合时,要把 action 的 scope 改为 prototype。
因为 Spring 默认 scope 是单例模式,这样只会创建一个 Action 对象,每次访问都是同一个Action对象,数据不安全。struts2 是要求每次次访问都对应不同的 Action,scope="prototype" 可以保证当有请求的时候都创建一个 Action 对象。

三、request

request、session 和 global session 类型只适用于 web 程序,通常是和 XmlWebApplicationContext 共同使用。

<bean id ="requestPrecessor"  class="...RequestPrecessor"    scope="request" />

Spring 容器,即 XmlWebApplicationContext 会为每个 HTTP 请求创建一个全新的 RequestPrecessor 对象。当请求结束后,该对象的生命周期即告结束,如同 java web 中 request 的生命周期。当同时有 10 个 HTTP 请求进来的时候,容器会分别针对这 10 个请求创建 10 个全新的 RequestPrecessor 实例,且它们之间互不干扰。简单来讲,request 可以看做 prototype 的一种特例,除了场景更加具体之外,语意上差不多。

四、session

对于 web 应用来说,放到 session 中最普遍的就是用户的登录信息。对于这种放到 session 中的信息,可以使用如下形式的制定 scope 为 session:

<bean id ="userPreferences"  class="...UserPreferences"    scope="session" />

Spring 容器会为每个独立的 session 创建属于自己的全新的 UserPreferences 实例,比 request scope 的 bean 会存活更长的时间,其他的方面没区别,如同 java web 中 session 的生命周期

五、global session

<bean id ="userPreferences" class="...UserPreferences" scope="globalsession" />

global session 只有应用在基于 porlet 的 web 应用程序中才有意义,它映射到 porlet 的 global 范围的 session,如果普通的 servlet 的 web 应用中使用了这个 scope,容器会把它作为普通的 session 的 scope 对待。

六、scope配置

1️⃣xml 方式:进行 bean 的配置时,指定 scope
xml配置bean

2️⃣注解方式:前提为配置 Spring 为注解配置
注解配置bean

七、bean 的初始化时机

熟悉了 Spring 容器管理的 bean 的作用域,接着就要思考一个问题:bean 到底是在什么时候进行实例化的?bean 对象无外乎是在以下两个时刻进行实例化的:

  1. Spring 容器启动时。
  2. 调用 getBean()时。

那么 bean 对象到底是在哪个时刻进行实例化的,这与 Bean 的作用域有着某种联系。为了能够清楚地看到 bean 对象的实例化,修改 PersonServiceBean 类的代码为:

public class PersonServiceBean implements PersonService {    
  public PersonServiceBean() {  
    System.out.println("我被实例化了");   
  }     
  @Override    
  public void save() {     
    System.out.println("我是save()"); 
  }
}

1️⃣当 Spring 的配置文件——beans.xml 的内容为:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="personService" class="cn.service.impl.PersonServiceBean"></bean> 

</beans>

即 bean 的作用域为 singleton 时,修改 SpringTest 类的代码为:

public class SpringTest {     
   @Test    
    public void test() {        
       // 实例化Spring容器   
       ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    } 
}

执行 test(),输出:

我被实例化了

这说明当 bean 的作用域为 singleton 时,bean 对象是在 Spring 容器启动时就进行创建了。即默认情况下会在容器启动时初始化 bean,但也可以指定 bean 节点的lazy-init=“true”来延迟初始化 bean。这时候,只有第一次获取 bean 会才初始化 bean。
如将 Spring 的配置文件——beans.xml 的内容改为:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">     

<bean id="personService" class="cn.service.impl.PersonServiceBean" lazy-init="true"></bean>

</beans>

lazy-init=”true”指定了不要在 Spring 容器启动时对这个 bean 进行实例化。此时,执行 test(),就不会输出这句话:“我被实例化了”。这时,只有将 SpringTest 类的代码修改为:

public class SpringTest {     
    @Test   
    public void test() {        
     //实例化Spring容器
     ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
     //从Spring容器取得bean
     PersonService personService = (PersonService) ctx.getBean("personService");
    } 
}

再次执行 test(),才会输出这句话:

我被实例化了

如果想对所有 bean 都应用延迟初始化,可以在根节点 beans 设置default-lazy-init=“true”,如下:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" 
default-lazy-init="true">     
...... 
</beans>

2️⃣当 Spring 的配置文件——beans.xml 的内容为:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"、
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">   
  
<bean id="personService" class="cn.service.impl.PersonServiceBean" 
scope="prototype"></bean> 

</beans>

即 bean 的作用域为 prototype 时,若 SpringTest 类的代码为:

public class SpringTest {     
  @Test    
  public void test() {        
    // 实例化Spring容器 
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
  } 
}

执行 test(),没有输出这句话:“> 我被实例化了”。这就说明了当 bean 的作用域为 prototype 时,bean 对象并不会在 Spring 容器启动时就进行创建。但是若将 SpringTest 类的代码改为:

public class SpringTest {     
    @Test    
     public void test() {        
       // 实例化Spring容器
       ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
       // 从Spring容器取得bean
       PersonService personService = (PersonService) ctx.getBean("personService"); 
  }
}

再次执行 test(),输出了这句话:

我被实例化了

证实了当 bean 的作用域为 prototype 时,bean 对象将会在调用 getBean() 时进行创建。

八、指定bean的初始化方法和销毁方法

为了达到在 bean 被初始化的时候,就初始化某些资源的目的,可修改 PersonServiceBean 类的代码为:

public class PersonServiceBean implements PersonService {
    public void init() {
        System.out.println("初始化某些资源");
    }
    public PersonServiceBean() {
        System.out.println("我被实例化了");
    }
    @Override
    public void save() {
        System.out.println("我是save()");
    }
}

这样,目的就具体地成为:当 Spring 容器初始化 PersonServiceBean 对象之后,就要执行该对象的 init()。为了达成这样的目的,只需修改 Spring 的配置文件—beans.xml 的内容为:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">    
 
<bean id="personService" class="cn.service.impl.PersonServiceBean" 
lazy-init="false" init-method="init" />

</beans>

若 SpringTest 类的代码为:

public class SpringTest {
    @Test
    public void test() {
        // 实例化Spring容器     
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    }
}

执行 test(),输出:
输出

现在又希望在 bean 被销毁的时候,就释放或关闭某些资源。为了达到这样的目的,可修改 PersonServiceBean 类的代码为:

public class PersonServiceBean implements PersonService {
    public void init() {
        System.out.println("初始化某些资源");
    }
    public PersonServiceBean() {
        System.out.println("我被实例化了");
    }
    @Override
    public void save() {
        System.out.println("我是save()");
    }
    public void destroy() {
        System.out.println("释放初始化的资源");
    }
}

bean 对象到底是什么时候销毁的呢?答案是:如果没有人为地删除它,默认该 bean 一直在 Spring 容器中,也就是说随着 Spring 容器的关闭,该 bean 才会被销毁。紧接着,要修改 Spring 的配置文件——beans.xml 的内容。

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">     

<bean id="personService" class="cn.service.impl.PersonServiceBean" 
lazy-init="false" init-method="init" destroy-method="destroy" /> 

</beans>

最后,修改测试类——SpringTest.java 为:

public class SpringTest {
    @Test
    public void test() {
        // ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
        // 实例化Spring容器         
        AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        // 正常关闭Spring容器  
        ctx.close();
    }
}

执行 test(),输出:
输出

这就是 Spring 管理的 Bean 的生命周期。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容