7.5.2 容器中Bean的作用域
7.5.2 容器中Bean的作用域
当通过Spring
容器创建一个Bean
实例时,不仅可以完成Bean
实例的实例化,还可以为Bean
指定特定的作用域。 Spring
支持如下6种作用域
作用域 | 描述 |
---|---|
singleton |
单例模式,在整个Spring IoC 容器中, singleton 作用域的Bean 将只生成一个实例。 |
prototype |
每次通过容器的getBean() 方法获取prototype 作用域的Bean 时,都将产生一个新的Bean 实例 |
request |
对于一次HTTP 请求,request 作用域的Bean 将只生成一个实例,这意味着,在同一次HTTP 请求内,程序每次请求该Bean ,得到的总是同一个实例。只有在web 应用中使用Spring 时,该作用域才真正有效。 |
session |
对于一次HTTP 会话,session 作用域的Bean 将只生成一个实例,这意味着,在同一次HTTP 会话内,程序每次请求该Bean ,得到的总是同一个实例。只有在Web 应用中使用Spring 时,该作用域才真正有效. |
application |
对应整个Web 应用,该Bean 只生成一个实例。这意味着,在整个Web 应用内,程序每次请求该Bean 时,得到的总是同一个实例。只有在web 应用中使用Spring 时,该作用域才真正有效。 |
websocket |
在整个WebSocket 的通信过程中,该Bean 只生成一个实例。只有在web 应用中使用Spring 时,该作用域才真正有效。 |
比较常用的是singleton
和prototype
两种作用域,
- 对于
singleton
作用域的Bean
,每次请求该Bean
都将获得相同的实例。容器负责跟踪Bean
实例的状态,负责维护Bean
实例的生命周期行为; - 如果一个
Bean
被设置成prototype
作用域,程序每次请求该id
的Bean, Spring
都会新建一个Bean
实例,然后返回给程序。在这种情况下,Spring
容器仅仅使用new
关键字创建Bean
实例,一旦创建成功,容器就不再跟踪实例,也不会维护Bean
实例的状态。
如果不指定Bean
的作用域, Spring
默认使用singleton
作用域。Java
在创建Java
实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此, prototype
作用域的Bean
的创建、销毁代价比较大。而singleton
作用域的Bean
实例一旦创建成功,就可以重复使用。因此,应该尽量避免将Bean
设置成prototype
作用域。
通过scope属性执行Bean的作用域
Spring
配置文件通过scope
属性指定Bean
的作用域,该属性可以接受singleton
、 prototype
、request
、session
和globalSession
五个值,分别代表上面介绍的5个作用域。
程序示例
1 | E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\scope |
下面的配置文件中配置singleton
作用域的Bean
和prototype
作用域Bean
各有一个。
beans.xml
1 |
|
从上面的代码中可以看到,
- 配置
p1
对象时没有指定scope
属性,则它默认是一个singleton
作用域的Bean
; - 而
p2
则指定了scope="prototype"
属性,这表明它是一个prototype
作用域的Bean
。 - 除此之外,上面配置文件还配置了一个
id
为date
的singleton
作用域的Bean
BeanTest.java
主程序通过如下代码来测试两个Bean
的区别。
1 | package lee; |
程序执行结果如下:
1 | true |
从上面的运行结果可以看出,
对于singleton
作用域的Bean
,每次请求该id
的Bean
,都将返回同个共享实例,因而两次获取的Bean
实例完全相同;
但对prototype
作用域的Bean
,每次请求该id
的Bean
都将产生新的实例,因此两次请求获得的Bean
实例不相同。
上面程序的最后还分两次获取,并输出Spring
容器中id
为date
的Bean
代表的时间,虽然程序获取、输出两个date
的时间相差一秒,但由于id
为date
的Bean
是一个singleton Bean
,该Bean
会随着容器的初始化而初始化——也就是在①号代码处, date Bean
已经被创建出来了,因此无论程序何时访问、输出date Bean
所代表的时间,永远输出①号代码的执行时间。
通过singleton属性指定Bean的作用域
早期指定Bean
的作用域也可通过singleton
属性指定,该属性只接受两个属性值:true
和false
,分别代表singleton
和prototype
作用域。使用singleton
属性则无法指定其他三个作用域,实际上Spring 2.x
不推荐使用singleton
属性指定Bean
的作用域, singleton
属性是Spring 1.2.x
的方式
request和session作用域
对于request
作用域,查看如下Bean
定义:
1 | <bean id="loginAction" class="org.crazyit.app.struts.LoginAction" scope="request"/> |
针对每次HTTP
请求,Spring
容器会创建一个全新的id
为loginAction
的Bean
实例,且该loginAction Bean
实例仅在当前HttpRequest
内有效。因此,如果程序需要,完全可以自由更改Bean
实例的内部状态;其他请求所获得的loginAction Bean
实例无法感觉到这种内部状态的改变。当处理请求结束时, request
作用域的Bean
实例将被销毁。request
、 session
作用域的Bean
只对Web
应用才真正有效。实际上通常只会将Web
应用的控制器Bean
指定成request
作用域。session
作用域与request
作用域完全类似,区别在于:request
作用域的Bean
对于每次HTTP
请求有效,而session
作用域的Bean
则对于每次Http Session
有效。request
和session
作用域只在Web
应用中才有效,并且必须在web
应用中增加额外配置才会生效。为了让request
和session
两个作用域生效,必须将HTTP
请求对象绑定到为该请求提供服务的线程上,这使得具有request
和session
作用域的Bean
实例能够在后面的调用链中被访问到。
如何使得request作用域生效
在Web
应用的web.xml
文件中增加如下Listener
配置,该Listener
负责使request
作用域生效:
1 | <listener> |
一旦在web.xml
中增加了如上所示配置,程序就可以在Spring
配置文件中使用request
或session
作用域了。下面的配置文件配置了一个实现类为Person
的Bean
实例,其作用域是request
。配置文件代码如下。
程序示例2
1 |
|
这样Spring
容器会为每次HTTP
请求生成一个Person
实例,当该请求响应结束时,该实例也随之消失。
Spring MVC默认支持request作用域
如果Web
应用直接使用Spring MVC
作为MVC
框架,即用Spring
的DispatcherServlet
或DispatcherPortlet
来拦截所有用户请求,则无须这些额外的配置,因为Spring
的DispatcherServlet
和DispatcherPortlet
已经处理了所有和请求有关的状态处理。
接下来本示例使用一个简单的JSP
脚本来测试该request
作用域,该JSP
脚本两次向Spring
容器请求获取id
为p
的Bean
-当用户请求访问该页面时,由于在同一个页面内,因此可以看到Spring
容器两次返回的是同一个Bean
。该JSP
脚本如下。
test.jsp
1 | <%@ page contentType="text/html; charset=GBK" language="java" |
使用浏览器请求该页面,将可以看到浏览器中输出如下内容:
1 | true |
如果读者再次刷该页面,将可以看到该页面依然输出true
,但程序访问、输出的Person Bean
不再是前一次请求得到的Bean
。
关于HttpRequest
和HttpSession
的作用范围,请参看第2章中关于Web
编程的介绍Spring5
不仅可以为Bean
指定已经存在的6个作用域,还支持自定义作用域。关于自定义作用域的内容,请参看Spring
官方文档等资料。