Spring Bean的作用域
Spring Bean的作用域
作用域 | 描述 |
---|---|
在每个Spring IoC容器中一个bean定义对应一个对象实例。 |
|
一个bean定义对应多个对象实例。 |
|
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring |
|
在一个HTTP |
|
在一个全局的HTTP |
Singleton作用域
当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
换言之,当把一个bean定义设置为singlton作用域时,Spring IoC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针
对该bean的后续请求和引用都将返回被缓存的对象实例。
Prototype作用域
Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()
方法)时都会创建一个新的bean实例。根据经验,对所
有有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
其他作用域
request
、session
以及global session
仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架)。
注意
下面介绍的作用域仅仅在使用基于web的Spring ApplicationContext
实现(如XmlWebApplicationContext
)时有用。如果在普通的Spring IoC容器中,比如像XmlBeanFactory
或ClassPathXmlApplicationContext
,尝试使用这些作用域,你将会得到一个IllegalStateException
异常(未知的bean作用域)。
初始化web配置
要使用request
、session
和 global session
作用域的bean(即具有web作用域的bean),在开始设置bean定义之前,还要做少量的初始配置。请注意,假如你只想要“常规的”作用域,也就是singleton和prototype,就不需要这一额外的设置。
在目前的情况下,根据你的特定servlet环境,有多种方法来完成这一初始设置。如果你使用的是Servlet 2.4及以上的web容器,那么你仅需要在web应用的XML声明文件web.xml
中增加下述ContextListener
即可
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener>
如果你用的是早期版本的web容器(Servlet 2.4以前),那么你要使用一个javax.servlet.Filter
的实现。请看下面的web.xml配置片段:
<filter> <filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class> </filter> <filter-mapping> <filter-name>requestContextFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
RequestContextListener
和RequestContextFilter
两个类做的都是同样的工作:将HTTP request对象绑定到为该请求提供服务的Thread
。这使得具有request和session作用域的bean能够在后面的调用链中被访问到。
Request作用域
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction
bean定义创建一个全新的LoginAction
bean实例,且该loginAction
bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction
bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
Session作用域
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session
,Spring容器会根据userPreferences
bean定义创建一个全新的userPreferences
bean实例,且该userPreferences
bean仅在当前HTTP Session
内有效。与request作用域
一样,你可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session
中根据userPreferences
创建的实例,将不会看到这些特定于某个HTTP Session
的状态变化。当HTTP Session
最终被废弃的时候,在该HTTP Session
作用域内的bean也会被废弃掉。
global session作用域
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
global session
作用域类似于标准的HTTP Session
作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session
的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session
作用域中定义的bean被限定于全局portlet Session
的生命周期范围内。
请注意,假如你在编写一个标准的基于Servlet的web应用,并且定义了一个或多个具有global session
作用域的bean,系统会使用标准的HTTP Session
作用域,并且不会引起任何错误。
作用域bean与依赖
能够在HTTP request或者Session
(甚至自定义)作用域中定义bean固然很好,但是Spring IoC容器除了管理对象(bean)的实例化,同时还负责协作者(或者叫依赖)的实例化。如果你打算将一个Http request范围的bean注入到另一个bean中,那么需要注入一个AOP代理来替代被注入的作用域bean。也就是说,你需要注入一个代理对象,该对象具有与被代理对象一样的公共接口,而容器则可以足够智能的从相关作用域中(比如一个HTTP request)获取到真实的目标对象,并把方法调用委派给实际的对象。
注意
<aop:scoped-proxy/>
不能和作用域为singleton
或prototype
的bean一起使用。为singleton bean创建一个scoped proxy将抛出BeanCreationException
异常。
让我们看一下将相关作用域bean作为依赖的配置,配置并不复杂(只有一行),但是理解“为何这么做”以及“如何做”是很重要的。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <aop:scoped-proxy/> </bean> <bean id="userService" class="com.foo.SimpleUserService"> <property name="userPreferences" ref="userPreferences"/> </bean>
在XML配置文件中,要创建一个作用域bean的代理,只需要在作用域bean定义里插入一个<aop:scoped-proxy/>
子元素即可(你可能还需要在classpath里包含CGLIB库,这样容器就能够实现基于class的代理;还可能要使用基于XSD的配置)。上述XML配置展示了“如何做”,现在讨论“为何这么做”。在作用域为request
、session
以及globalSession
的bean定义里,为什么需要这个<aop:scoped-proxy/>
元素呢?下面我们从去掉<aop:scoped-proxy/>
元素的XML配置开始说起:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
从上述配置中可以很明显的看到singleton bean userManager
被注入了一个指向HTTP Session
作用域bean userPreferences
的引用。singleton userManager
bean会被容器仅实例化一次,并且其依赖(userPreferences
bean)也仅被注入一次。这意味着,userManager
在理论上只会操作同一个userPreferences
对象,即原先被注入的那个bean。而注入一个HTTP Session
作用域的bean作为依赖,有违我们的初衷。因为我们想要的只是一个userManager
对象,在它进入一个HTTP Session
生命周期时,我们希望去使用一个HTTP Session
的userPreferences
对象。
当注入某种类型对象时,该对象实现了和UserPreferences
类一样的公共接口(即UserPreferences
实例)。并且不论我们底层选择了何种作用域机制(HTTP request、Session
等等),容器都会足够智能的获取到真正的
UserPreferences
对象,因此我们需要将该对象的代理注入到userManager
bean中, 而userManager
bean并不会意识到它所持有的是一个指向UserPreferences
引用的代理。在本例中,当UserManager
实例调用了一个使用UserPreferences
对象的方法时,实际调用的是代理对象的方法。随后代理对象会从HTTP Session
获取真正的UserPreferences
对象,并将方法调用委派给获取到的实际的UserPreferences
对象。
自定义作用域
详情百度,知道有这么个东西就行了