我们不但可以控制bean的依赖及配置,还可以控制bean的作用域。
Bean作用域 Scope
Spring支持5种bean的作用域,(在web application中可以使用其中的3种):
- singleton —— 默认为每个IoC容器单例(一个context,一个bean)
- prototype —— 可以是任意数量的示例
- request —— bean的生命周期在一个HTTP request中。即每个Http Request有其自有的bean示例;(该作用域仅适用于基于web的Spring ApplicationContext)
- session —— bean的生命周期在一个HTTP Session中;(该作用域仅适用于基于web的Spring ApplicationContext)
- global session —— bean的生命周期在一个全局的HTTP Session中;(适用于portlet context)
- application —— bean的生命周期在一个ServletContext中;(该作用域仅适用于基于web的Spring ApplicationContext)
- (在Spring 3.0中,可以使用thread scope,但是默认没有注册该种scope)
singleton
在Spring容器中只有一个共享的bean的单件实例。
单件bean存放在单件benas的缓存中;在创建之后请求访问该bean将直接返回缓存的bean对象。
Spring中的单件概念与设计模式中的不同,设计模式中的对象作用域是hard-codes的,保证在每个特定的ClassLoader中只有一个类的实例被创建。而Spring容器中的单件为每个容器拥有一个bean的实例。
单件作用域是Spring的默认作用域;可以在XML中如下配置单件:
<bean id="accountService" class="com.foo.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
prototype
使用prototype作用域,则每次该bean,都会生成一个该bean的新的实例。
常见的使用规则是:对于有状态的bean,使用prototype;对于无状态的bean,使用singleton;
在XML中,如下配置prototype:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
与其他的scope不同的是,Spring不会负责管理prototype scope的生命周期;Spring容器实例化,配置,组装prototype bean,然后将其交给客户程序去处理,而不会在容器中继续维护该实例的记录。
因此,虽然所有的生命周期初始化回调函数会在所有的scope上被调用,但是,prototype的destruction 生命周期回调函数不会被调用。客户代码必须负责清理prototype scope的对象,并释放prototype对象持有的资源。为了使Spring容器释放prototype bean持有的资源,可以使用自定义的 bean post-processor,其持有一个需要清理的bean的引用。
Singleton bean中的prototype dependencies
当在singleton中使用prototype的依赖时,依赖在初始化的时候被解析。因此,当prototype bean被注入singleton bean中时,一个新的prototype bean被实例化并注入给singleton bean中。该prototype bean为该单件bean的prototype的唯一示例。
Request, session, global session, application
这些scope只可以使用在基于web的Spring ApplicationContext实现中(如XmlWebApplicationContext)。如果在普通的Spring IoC容器中使用这些scope,则会抛出一个IllegalStateException异常(未知的bean scope)。
初始化web配置
为了支持request, session, global session scope的bean,需要在bean定义之前进行一些配置(这些初始化的设置在standard scope中时是不需要的)。
这些初始化设置依赖于特定的Servlet环境:
如果在Spring Web MVC中的访问request scope的bean,由DispatcherServelt或者DispatcherPortlet来完成。
如果使用Servlet 2.5 web 容器,在Spring DispatcherServlet外处理的request,需要注册org.springframework.web.context.request.RequestContextLinstener, ServletRequestListener。
对于Servlet3.0+,可以在WebApplicationInitializer接口中编程完成。
对于更旧版本的web容器,在web.xml中添加下列的声明:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
如果listener的设置有问题的话,可以考虑使用RequestContextFilter,filter mapping依赖于具体的使用环境:
<web-app>
...
<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>
...
</web-app>
DispatcherServelt, RequestContextListener, RequestContextFilter做的事情是一样的,即将HTTP Request对象绑定到处理该request的Thread,这使得request session作用域的bean在之后的调用链中可用。
Request scope
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器在每一个HTTP Request中创建一个该bean的新的实例。这些bean的实例为每个独立的quest所拥有。当request完成处理的时候,request scope bean将会被回收。
Session scope
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
在每一个HTTP Session中,Spring容器创建了一个session scope的bean的示例。Request与Session scope的bean的行为类似。
Global session scope
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
global session scope与session scope类似,其仅应用在基于portlet的web application中。
如果在标准的基于 servlet 中web application中,定义了global session scope的 beans,实际上使用的是 session scope。
Application scope
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
application scope bean的作用范围为整个web application,该bean被存储为ServletContext的一个属性。
application scope bean 与 Spring 单例bean有些相似,但是有两点不同:
- application scope bean是每个ServletContext中一个单件,而不是每个Spring Application Context中一个
- application scope bean为每个ServletContext的属性
scoped beans作为其他bean的依赖
对于 singleton 或者 prototype scope bean,如果其作为其他bean的依赖,不需要使用 <aop:scoped-proxy/>.
当使用request- session- globalSession- scoped 的beans作为其他bean的依赖,按如下配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
自定义scopes
我们可以自定义scope,甚至可以重载现存的scope。
创建自定义scope
需要实现 org.springframework.beans.factory.config.Scope接口。
Scope接口有4个方法用于获取对象,移除对象,销毁对象。
使用自定义scope
使用下列方法在Spring容器中注册自定义Scope:
void registerScope(String scopeName, Scope scope);
该方法位于ApplicationContext的实现中。
例如:
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
<bean id="..." class="..." scope="thread">
除了使用编程方法注册scope,可以使用CustomScopeConfigurer类进行scope的注册:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="bar" class="x.y.Bar" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="foo" class="x.y.Foo">
<property name="bar" ref="bar"/>
</bean>
</beans>