1.5. Bean Scopes
Spring可以帮助我们装配对象,从而促使系统工作。另外,Spring还可以帮助我们管理对象的生命周期,Spring称之为Bean Scopes。Spring支持6种Scope,其中4种只能用于spring web。
Bean scopes:
Scope | Description |
---|---|
singleton | 单例 |
prototype | 每次使用都生成新实例,实例数目不限 |
request | 每次HTTP请求时生成实例,单个请求中为单例 |
session | 单个HTTP session中为单例 |
application | 单个ServletContext中为单例 |
webSocket | 单个websocket中为单例 |
后四种scope针对web应用。
1.5.1. The Singleton Scope
单例(很基础,不多说了):
Spring中的单例跟设计模式中的单例稍微有点区别的地方在于,Spring中的单例是per-container and per-bean(IoC Container中的单例)。Spring中的默认scope是singleton,所以如果不指定bean的scope,那么获取的一定是在Spring中全局唯一的那个单例。
xml配置示例:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService"
scope="singleton"/>
1.5.2. The Prototype Scope
注意,请不要被上图中的accountDao迷惑,因为accountDao一定是典型的单例,这块只是为了复用之前的单例图:
(A data access object (DAO) is not typically configured as a prototype, because a typical DAO does not hold any conversational state. It was easier for us to reuse the core of the singleton diagram.)
使用prototype scope的原则:
As a rule, you should use the prototype scope for all stateful beans and the singleton scope for stateless beans.
配置示例:
<bean id="accountService" class="com.something.DefaultAccountService"
scope="prototype"/>
关于prototype,需要注意的是,Spring只会每次生成实例后就将实例交给用户,用户需要负责销毁实例和释放实例资源的工作,而Spring只负责初始化实例时的工作。prototype在某种程度上可以等价为java的new操作,但是new完之后实例的生命周期需要由用户控制。本节提到可以通过实现bean post-processor来让Spring管理prototype scope对象的生命周期,不过本节没有说明如何实现。
1.5.3. Singleton Beans with Prototype-bean Dependencies
在Spring中如果试图将Prototype-bean注入到Singleton Bean中,那么只会注入一次,不会发生每次使用单例时都给用户重新生成Prototype-bean的事情。如果真的有这种需求,那么可以参考Method Injection。
1.5.4. Request, Session, Application, and WebSocket Scopes
request, session, application, and websocket scopes。这四种scope必须在web类型的ApplicationContext(比如XmlWebApplicationContext)下才能正常工作,假设使用常规的ClassPathXmlApplicationContext容器,那么直接报IllegalStateException异常并提示错误。
由于还没看过spring web方面的文档,此节后续再补充。
1.5.5. Custom Scopes
用户自定义scope。Spring支持用户自定义scope类型,甚至可以覆盖重写原有类型,当然,覆盖重写原有类型Spring官方是不推荐的。
实现用户自定义scope,需要继承实现Scope接口。
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.lang.Nullable;
public interface Scope {
Object get(String var1, ObjectFactory<?> var2);
@Nullable
Object remove(String var1);
void registerDestructionCallback(String var1, Runnable var2);
@Nullable
Object resolveContextualObject(String var1);
@Nullable
String getConversationId();
}
接口的文档说明可以参考:
Scope接口
官方文档建议直接看源码的具体实现。比如RequestScope。
package org.springframework.web.context.request;
import org.springframework.lang.Nullable;
public class RequestScope extends AbstractRequestAttributesScope {
public RequestScope() {
}
protected int getScope() {
return 0;
}
@Nullable
public String getConversationId() {
return null;
}
}
RequestScope没什么具体实现,干货都在AbstractRequestAttributesScope里面。getScope返回0,这个应该是类似用来枚举不同的scope,不同的值代表不同的scope类型,0代表request scope。
package org.springframework.web.context.request;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;
public abstract class AbstractRequestAttributesScope implements Scope {
public AbstractRequestAttributesScope() {
}
public Object get(String name, ObjectFactory<?> objectFactory) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, this.getScope());
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, this.getScope());
Object retrievedObject = attributes.getAttribute(name, this.getScope());
if (retrievedObject != null) {
scopedObject = retrievedObject;
}
}
return scopedObject;
}
@Nullable
public Object remove(String name) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, this.getScope());
if (scopedObject != null) {
attributes.removeAttribute(name, this.getScope());
return scopedObject;
} else {
return null;
}
}
public void registerDestructionCallback(String name, Runnable callback) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
attributes.registerDestructionCallback(name, callback, this.getScope());
}
@Nullable
public Object resolveContextualObject(String key) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
return attributes.resolveReference(key);
}
protected abstract int getScope();
}
重要方法详解:
Object get(String name, ObjectFactory objectFactory)
用于获取bean,代码很简单,一看就懂,RequestScope的实现中,最终将生成的bean对象保存在RequestContextHolder类中名为requestAttributesHolder成员变量中,requestAttributesHolder的类型为ThreadLocal,ThreadLocal保证了线程中的bean唯一,从而才保证了bean是request scope级别。讲的更清楚点:相当于每次请求所在的线程中,该对象为单例。根据Spring之前给Container的单例解释,我给request scope中的bean取了个类似的英文名叫做per-thread and per-bean。
Object remove(String name)
get方法的反向操作。
void registerDestructionCallback(String name, Runnable destructionCallback)
注册回调,负责对象的一些销毁工作,同样是注册到ThreadLocal中。
Using a Custom Scope
当用户自定义scope实现完成之后,必须注册后才能使用。SimpleThreadScope是Spring自带的scope类型实现,但是默认不会注册,下面代码示例将会把这个scope注册进去。
ApplicationContext context = new ClassPathXmlApplicationContext( "scope.xml");
((ClassPathXmlApplicationContext) context).getBeanFactory().registerScope("thread", new SimpleThreadScope());
上面示例是代码的注册方式,还可以使用xml的配置注册方式,如下:
<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>
使用的话,跟指定Spring自带的scope没什么两样:
<bean id="..." class="..." scope="thread">
总结一下,Spring默认支持6种scope,其中4种scope必须在web类型的Container中才能使用,同时还支持用户自定义scope。