1. singleton
配置中的bean定义可以看作是一个模板,容器会根据这个模板来构造对象。但是要根据这个模板构造多少对象实例,又该让这些构造完的对象实例存活多久,则由容器根据bean定义的scope语意来决定。标记为拥有singleton scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出,也就是说,它与IoC容器"几乎"拥有相同的"寿命"。
需要注意的一点是,不要因为名字的原因而与GoF 所提出的Singleton模式相混淆,二者的语意是不同的: 标记为singleton的bean是由容器来保证这种类型的bean在同一个容器中只存在一个共享实例; 而Singleton模式则是保证在同一个Classloader中只存在一个这种类型的实例。
可以从两个方面来看待singleton的bean所具有的特性。
对象实例数量。singleton类型的bean定义,在一个容器中只存在一个共享实例,所有对该类型bean的依赖都引用这一单一实例。这就好像每个幼儿园都会有一个滑梯一样,这个幼儿园的小朋友共同使用这一个滑梯。而对于该幼儿园容器来说,滑梯实际上就是一个singleton的bean。
对象存活时间。singleton类型bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。
2. prototype
针对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每次返回给请求方一个新的对象实例之后,就任由这个对象实例"自生自灭"了。
所以,对于那些请求方不能共享使用的对象类型,应该将其bean定义的scope设置为prototype。这样,每个请求方可以得到自己对应的一个对象实例。通常,声明为prototype的scope的bean定义类型,都是一些有状态的,比如保存每个顾客信息的对象。
3. request、session和global session
这三个scope类型是Spirng 2.0之后新增加的,它们不像之前的singleton和prototype那么"通用",因为它们只适用于Web应用程序,通常是与XmlWebApplicationContext共同使用,而这些将在第6部分详细讨论。不过,既然它们也属于scope的概念,这里就简单提几句。
注意 只能使用scope 属性才能指定这三种"bean的scope类型"。也就是说,你不得不使用基于XSD文档声明的XML配置文件格式。
request
request通常的配置形式如下:
- <bean id="requestProcessor" class="...
RequestProcessor" scope="request"/>
Spring容器,即XmlWebApplicationContext会为每个HTTP请求创建一个全新的Request- Processor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求返回10个全新的RequestProcessor对象实例,且它们之间互不干扰。从不是很严格的意义上说,request可以看作prototype的一种特例,除了场景更加具体之外,语意上差不多。
session
对于Web应用来说,放到session中的最普遍的信息就是用户的登录信息,对于这种放到session中的信息,我们可使用如下形式指定其scope为session:
- <bean id="userPreferences" class="com.foo.
UserPreferences" scope="session"/>
Spring容器会为每个独立的session创建属于它们自己的全新的UserPreferences对象实例。与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面真是没什么差别。
global session
还是userPreferences,不过scope对应的值换一下,如下所示:
- <bean id="userPreferences" class="com.foo.
UserPreferences" scope="globalSession"/>
global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。如果在普通的基于servlet的Web应用中使用了这个类型的scope,容器会将其作为普通的session类型的scope对待。
4. 自定义scope类型
在Spring 2.0之后的版本中,容器提供了对scope的扩展点,这样,你可以根据自己的需要或者应用的场景,来添加自定义的scope类型。需要说明的是,默认的singleton和prototype是硬编码到代码中的,而request、session和global session,包括自定义scope类型,则属于可扩展的scope行列,它们都实现了org.springframework.beans.factory.config.Scope接口,该接口定义如下:
- public interface Scope {
- Object get(String name, ObjectFactory objectFactory);
- Object remove(String name);
- void registerDestructionCallback(String name,
Runnable callback); - String getConversationId();
- }
要实现自己的scope类型,首先需要给出一个Scope接口的实现类,接口定义中的4个方法并非都是必须的,但get和remove方法必须实现。我们可以看一下http://www.jroller.com/eu/entry/implementing _efficinet_id_generator中提到的一个ThreadScope的实现(见代码清单4-28)。
代码清单4-28 自定义的ThreadScope的定义
- public class ThreadScope implements Scope {
- private final ThreadLocal threadScope = new ThreadLocal() {
- protected Object initialValue() {
- return new HashMap();
- }
- };
- public Object get(String name, ObjectFactory objectFactory) {
- Map scope = (Map) threadScope.get();
- Object object = scope.get(name);
- if(object==null) {
- object = objectFactory.getObject();
- scope.put(name, object);
- }
- return object;
- }
- public Object remove(String name) {
- Map scope = (Map) threadScope.get();
- return scope.remove(name);
- }
- public void registerDestructionCallback(String name,
Runnable callback) { - }
- ...
- }
更多Scope相关的实例,可以参照同一站点的一篇文章"More fun with Spring scopes"(http://jroller. com/eu/entry/more_fun_with_spring_scopes),其中提到PageScope的实现。
有了Scope的实现类之后,我们需要把这个Scope注册到容器中,才能供相应的bean定义使用。通常情况下,我们可以使用ConfigurableBeanFactory的以下方法注册自定义scope:
- void registerScope(String scopeName, Scope scope);
其中,参数scopeName就是使用的bean定义可以指定的名称,比如Spring框架默认提供的自定义scope类型request或者session。参数scope即我们提供的Scope实现类实例。
对于以上的ThreadScope,如果容器为BeanFactory类型(当然,更应该实现Configurable- BeanFactory),我们可以通过如下方式来注册该Scope:
- Scope threadScope = new ThreadScope();
- beanFactory.registerScope("thread",threadScope);
之后,我们就可以在需要的bean定义中直接通过"thread"名称来指定该bean定义对应的scope为以上注册的ThreadScope了,如以下代码所示:
- <bean id="beanName" class="..." scope="thread"/>
除了直接编码调用ConfigurableBeanFactory的registerScope来注册scope,Spring还提供了一个专门用于统一注册自定义scope的BeanFactoryPostProcessor实现(有关BeanFactoryPost- Processor的更多细节稍后将详述),即org.springframework.beans.factory.config.Custom- ScopeConfigurer。对于ApplicationContext来说,因为它可以自动识别并加载BeanFactoryPost- Processor,所以我们就可以直接在配置文件中,通过这个CustomScopeConfigurer注册来Thread- Scope(如代码清单4-29所示)。
代码清单4-29 使用CustomScopeConfigurer注册自定义scope
- <bean class="org.springframework.beans.factory.
config.CustomScopeConfigurer"> - <property name="scopes">
- <map>
- <entry key="thread" value="com.foo.ThreadScope"/>
- </map>
- </property>
- </bean>
在以上工作全部完成之后,我们就可以在自己的bean定义中使用这个新增加到容器的自定义scope"thread"了,如下代码演示了通常情况下"thread"自定义scope的使用:
- <bean id="beanName" class="..." scope="thread">
- <aop:scoped-proxy/>
- </bean>
<aop:scoped-proxy/>:通过代理引用该对象