1. 程式人生 > >Spring學習筆記——Spring Scope(作用域)詳解

Spring學習筆記——Spring Scope(作用域)詳解

引言

Spring學習筆記 —— 從IOC說起中,曾經提到過Spring中的Bean是根據Scope(作用域)來生成的,但是一直都沒有詳細解釋過,除了Singleton(單例)和prototype(原型)作用域之外,另外一種重要的作用域——使用者自定義作用域。

今天要寫的就是如何自定義一個作用域,且如何在作用域內對Bean進行管理。本文還是會分成三部分,示例(包含一個簡單的程式碼示例),程式碼解析(包含類圖分析)和小結。

示例

首先是我們簡單的Bean,SimpleBean.java這個類中有一個私有變數createdTime,在每次例項化的時候,都會被賦值為當前的時間戳,因此可以根據時間戳來判斷是否屬於同一個Bean。

public class BeanSample {
    private Long createdTime;

    public BeanSample() {
        createdTime = (new Date()).getTime();
    }

    public void printTime() {
        System.out.println(createdTime);
    }
}

然後是ScopeSample,我們自定義的作用域也要宣告稱一個Bean,在類實現裡面,需要實現介面Scope.java

public class ScopeSample
implements Scope{
//這裡做了一個簡單的處理,直接用HashMap儲存Bean,且為每一個使用者,根據使用者的userId,建立一個Bean的HashMap private final Map<Long, Map<String, Object>> scopeMaps = new ConcurrentHashMap<Long, Map<String,Object>>(); //簡單實現,直接將userId定義為pulic static public static Long curUserId = 1L; //get方法,必須實現
@Override public Object get(String name, ObjectFactory<?> objectFactory) { Map<String, Object> objectMap; if(scopeMaps.get(curUserId) == null) { Map<String,Object> newObjMap = new ConcurrentHashMap<String,Object>(); scopeMaps.put(curUserId, newObjMap); objectMap = newObjMap; } else { objectMap = scopeMaps.get(curUserId); } if(!objectMap.containsKey(name)) { objectMap.put(name, objectFactory.getObject()); } return objectMap.get(name); } //remove方法,必須實現。將某個Bean從當前的作用域移除。 @Override public Object remove(String name) { Map<String, Object> objectMap = scopeMaps.get(curUserId); if(objectMap != null) { return objectMap.remove(name); } return null; } //選擇實現,當某個特定的Bean從作用域中移除之後的回撥函式。 @Override public void registerDestructionCallback(String name, Runnable callback) { } //可選,返回某個指定Bean所處的上下文(context) @Override public Object resolveContextualObject(String key) { return null; } //可選,返回當前這個作用域的ID @Override public String getConversationId() { return null; } }

接下來是Bean的定義。scopeBean.xml

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <!--首先,我們的作用域物件也是一個Bean,這個Bean是預設的單例 -->
    <bean id="sampleScope" class="com.stduy.scope.ScopeSample"></bean>
    <!--然後,我們的簡單Bean也要宣告為一個Bean,但它的作用域就是我們自定義的作用域了-->
    <bean id="beanSample" class="com.stduy.scope.BeanSample" scope="sampleScope">
    </bean>
    <!--使用CustomScopeConfigure進行自定義作用域,其中key就是我們自定義作用域的名稱。 -->
    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="sampleScope">
                    <ref bean="sampleScope" />
                </entry>
            </map>
        </property>
    </bean>

</beans>

然後是我們的主函式。ScopeMain.java

public class ScopeMain {
    public static Long curUserId = 1L;
    public static void main(String args[]) {
        ApplicationContext app = new ClassPathXmlApplicationContext("scopeSample.xml");

        BeanSample bean = app.getBean(BeanSample.class);

        bean.printTime();
        //1478129214619
        bean = app.getBean(BeanSample.class);

        bean.printTime();
        //1478129214619 未改變userId的時候,始終返回同一個Bean
        ScopeSample.curUserId = 2L; //改變userID

        bean = app.getBean(BeanSample.class);

        bean.printTime();
        //1478129214621 得到新的Bean。
    }
}

原始碼解析

類圖分析

ScopeBean

這個類圖比較清晰地描述了BeanFactory和Scope之間的關係。DeafultListableBeanFactory持有0個到n個Scope,這種包含關係的宣告,是在AbstractBeanFactory中完成的。

作用域註冊程式碼解析

在前面我們提到過,我們是宣告一個 CustomScopeConfigurer,並且將Scope田間道其建構函式引數中完成配置的。

那麼,先來看看這個類的實現。

public class CustomScopeConfigurer implements BeanFactoryPostProcessor, BeanClassLoaderAware, Ordered 

原來是一個實現了BeanFactoryPostProcessor介面的Bean,那麼接下來看postProcessBeanFactory方法。

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (this.scopes != null) {
            //對宣告的所有scope進行處理(scope是一個String,Object的Map
            for (Map.Entry<String, Object> entry : this.scopes.entrySet()) {
                String scopeKey = entry.getKey();
                Object value = entry.getValue();
                //使用Scope Bean作為Value,可以直接註冊,也就是上文的示例中提到的。
                if (value instanceof Scope) {
                    beanFactory.registerScope(scopeKey, (Scope) value);
                }
                //也可以使用Class進行註冊,這個時候會對Class進行例項化。
                else if (value instanceof Class) {
                    Class<?> scopeClass = (Class<?>) value;
                    Assert.isAssignable(Scope.class, scopeClass);
                    beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
                }
                //還可以使用字串進行例項化,使用字串例項化的時候首先會把字串轉化為Class,後面的就同Class註冊一樣了。
                else if (value instanceof String) {
                    Class<?> scopeClass = ClassUtils.resolveClassName((String) value, this.beanClassLoader);
                    Assert.isAssignable(Scope.class, scopeClass);
                    beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
                }
                else {
                    throw new IllegalArgumentException("Mapped value [" + value + "] for scope key [" +
                            scopeKey + "] is not an instance of required type [" + Scope.class.getName() +
                            "] or a corresponding Class or String value indicating a Scope implementation");
                }
            }
        }
    }

然後我們再來看看AbstractBeanFactory中的registerScope做了什麼事情。

public void registerScope(String scopeName, Scope scope) {
        Assert.notNull(scopeName, "Scope identifier must not be null");
        Assert.notNull(scope, "Scope must not be null");
        //不允許宣告Spring中自帶的兩種Scope,singleton和prototype
        if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
            throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
        }
        //簡單地把Scope物件加入到Map中。如果重複註冊,則以最後一個註冊的為準。
        Scope previous = this.scopes.put(scopeName, scope);
        //省略debug輸出
    }

通過以上程式碼,作用域物件的註冊就完成了。

作用域物件的生成

生成並註冊了作用域之後,自然就是生成我們需要的Bean物件了。

也就是在AbstractBeanFactory.doGetBean方法中。因為前文已經包含了前面程式碼的分析了,這裡就只展示跟Bean Scope相關的了。

String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
    //在這裡,新建一個匿名內部類,實現ObjectFacotry介面。作為引數傳入到Scope的get方法中。
    Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
        //直接呼叫了BeanFactory的beforeCreate,create和AfterCreate。
        @Override
        public Object getObject() throws BeansException {
            beforePrototypeCreation(beanName);
            try {
                return createBean(beanName, mbd, args);
            }
            finally {
                afterPrototypeCreation(beanName);
            }
        }
    });
    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
    throw new BeanCreationException(beanName,
            "Scope '" + scopeName + "' is not active for the current thread; consider " +
            "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
            ex);
}

從上面的程式碼我們可以看到,其實對於Bean的beforeCreate, afterCraete, Create,Scope並不進行管理,Scope只是負責對應的物件存取。

小結

這篇文章介紹了Spring的自定義Scope,也就是自定義的作用域來管理Bean。BeanFactory持有零個或多個Scope物件。在Scope物件中,我們不會對Bean的建立進行任何干預,只是負責根據一定的規則,對Bean進行儲存和取用。

有了Scope Bean,我們就能夠針對使用者/使用者組進行Bean儲存,而不僅僅是所有人使用同一個Bean(singleton),又或者是全部人使用不同的Bean了(prototype)。