The IoC Container 1.5

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

单例(很基础,不多说了):


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

prototype

注意,请不要被上图中的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异常并提示错误。


非法的scope

由于还没看过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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343