一、作用域和生命周期
作用域
作用域限定了Spring Bean的作用范围,在Spring配置文件定义Bean时,通过声明scope配置项,可以灵活定义Bean的作用范围。例如,当希望每次IOC容器返回的Bean是同一个实例时,可以设置scope为singleton;当希望每次IOC容器返回的Bean实例是一个新的实例时,可以设置scope为prototype。scope配置项有5个属性,用于描述不同的作用域:singleton、prototype、request、session、global-session。
生命周期
Spring 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容器一旦把这个对象交给请求方之后,就不再管理这个对象了。
最典型的体现就是spring与struts2进行整合时,要把action的scope改为prototype。
因为spring 默认scope 是单例模式,这样只会创建一个Action对象,每次访问都是同一个Action对象,数据不安全。struts2是要求每次次访问都对应不同的Action,scope="prototype" 可以保证当有请求的时候都创建一个Action对象。
如同分苹果,将苹果的bean的scope属性声明为prototype,在每个人领取苹果的时候,我们都是发一个新的苹果给他,发完之后,别人爱怎么吃就怎么吃,爱什么时候吃什么时候吃,但是注意吃完要把苹果核扔到垃圾箱!对于那些不能共享使用的对象类型,应该将其定义的scope设为prototype。
三、request
request、session和global session类型只适用于web程序,通常是和XmlWebApplicationContext共同使用。
Spring容器,即XmlWebApplicationContext 会为每个HTTP请求创建一个全新的RequestPrecessor对象,当请求结束后,该对象的生命周期即告结束,如同java web中request的生命周期。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求创建10个全新的RequestPrecessor实例,且它们之间互不干扰,简单来讲,request可以看做prototype的一种特例,除了场景更加具体之外,语意上差不多。
四、session
对于web应用来说,放到session中最普遍的就是用户的登录信息,对于这种放到session中的信息,我们可以使用如下形式的制定scope为session:
Spring容器会为每个独立的session创建属于自己的全新的UserPreferences实例,比request scope的bean会存活更长的时间,其他的方面没区别,如同java web中session的生命周期。
五、global session
global session只有应用在基于porlet的web应用程序中才有意义,它映射到porlet的global范围的session,如果普通的servlet的web 应用中使用了这个scope,容器会把它作为普通的session的scope对待。
六、scope配置
1)xml方式
进行bean的配置时,指定scope。
2)注解方式
前提为配置spring为注解配置。
七、bean的初始化时机
熟悉了Spring容器管理的bean的作用域,接着就要思考一个问题:bean到底是在什么时候进行实例化的?
bean对象无外乎是在以下两个时刻进行实例化的:
- 调用getBean()方法时。
- Spring容器启动时。
那么bean对象到底是在哪个时刻进行实例化的,这与Bean的作用域有着某种联系。我们以配置Spring管理的bean的作用域的案例为基础进行深入探讨。为了能够清楚地看到bean对象的实例化,修改PersonServiceBean类的代码为:
public class PersonServiceBean implements PersonService {
public PersonServiceBean() {
System.out.println("我被实例化了");
}
@Override
public void save() {
System.out.println("我是save()方法");
}
}
- 当Spring的配置文件——beans.xml的内容为:
即bean的作用域为singleton时,修改SpringTest类的代码为:
public class SpringTest {
@Test
public void test() {
// 实例化Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
}
}
此时,测试test()方法,Eclipse控制台输出:
我被实例化了
这说明了当bean的作用域为singleton时,bean对象是在Spring容器启动时就进行创建了。即默认情况下会在容器启动时初始化bean,但也可以指定bean节点的lazy-init=“true”来延迟初始化bean。这时候,只有第一次获取bean会才初始化bean。
如我们将Spring的配置文件——beans.xml的内容改为:
lazy-init=”true”指定了不要在Spring容器启动时对这个bean进行实例化。
此时,测试test()方法,Eclipse控制台根本就不会输出这句话:“>我被实例化了”
这时,只有将SpringTest类的代码修改为:
public class SpringTest {
@Test
public void test() {
//实例化Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
//从Spring容器取得bean
PersonService personService = (PersonService) ctx.getBean("personService");
}
}
再次测试test()方法,Eclipse控制台才会输出这句话:
我被实例化了
如果想对所有bean都应用延迟初始化,可以在根节点beans设置default-lazy-init=“true”,如下:
......
- 当Spring的配置文件——beans.xml的内容为:
即bean的作用域为prototype时,若SpringTest类的代码为:
public class SpringTest {
@Test
public void test() {
// 实例化Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
}
}
测试test()方法,可以发现Eclipse控制台没有输出这句话:“> 我被实例化了”
这就说明了当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()方法,可以发现Eclipse控制台输出了这句话:
我被实例化了
证实了当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的内容为:
若SpringTest类的代码为:
public class SpringTest {
@Test
public void test() {
// 实例化Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
}
}
测试test()方法,Eclipse控制台将打印:
现在我们又希望在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的内容。
最后,我们要修改测试类——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()方法,Eclipse控制台将打印:
这就是Spring管理的Bean的生命周期。