一、作用域(scope)和生命周期
作用域
作用域限定了 Bean 的作用范围。在 Spring 配置文件定义 Bean 时,通过声明 scope 配置项,可以灵活定义 Bean 的作用范围。例如,当希望每次 IOC 容器返回的 Bean 是同一个实例时,可以设置 scope 为 singleton;当希望每次 IOC 容器返回的 Bean 实例是一个新的实例时,可以设置 scope 为 prototype。scope 配置项有 5 个属性,用于描述不同的作用域:singleton、prototype、request、session、global-session。说明如下:
- singleton:唯一 bean 实例,Spring 中的 bean 默认都是单例的。
- prototype:每次请求都会创建一个新的 bean 实例。
- request:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
- session:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
生命周期
Bean 的生命周期只有四个阶段(实例化 -> 属性赋值 -> 初始化 -> 销毁)。实例化和属性赋值对应构造方法和 setter 方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
主要逻辑都在 doCreate() 中,逻辑很清晰,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应。
1️⃣createBeanInstance() -> 实例化
2️⃣populateBean() -> 属性赋值
3️⃣initializeBean() -> 初始化
查看源码能证明实例化、属性赋值和初始化这三个生命周期的存在。至于销毁,是在容器关闭时调用的,详见ConfigurableApplicationContext#close()。
二、singleton(单例)
- 默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。
- 在创建对象的时候(先调用构造器),会去调用
init-method=".."
属性值中所指定的方法。 - 对象在被销毁的时候,会调用
destroy-method="..."
属性值中所指定的方法(例如调用 container.destroy() 的时候)。 -
lazy-init="true"
,可以让这个对象在第一次被访问的时候创建,而不是被加载的时候。
此外,singleton 类型的 bean 定义从容器启动到第一次被请求而实例化开始,只要容器不销毁或退出,该类型的 bean 的单一实例就会一直存活。典型单例模式,如同 servlet 在 web 容器中的生命周期。
三、prototype(原型)
- Spring 读取 xml 文件的时候,不会创建对象。
- 在每一次访问这个对象的时候,Spring 容器都会创建这个对象,并且调用
init-method=".."
属性值中所指定的方法。 - 对象销毁的时候,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七、bean 的初始化时机
熟悉了 Spring 容器管理的 bean 的作用域,接着就要思考一个问题:bean 到底是在什么时候进行实例化的?bean 对象无外乎是在以下两个时刻进行实例化的:
- Spring 容器启动时。
- 调用 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 的生命周期。