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 的生命周期。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容