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。
}
}
原始碼解析
類圖分析
這個類圖比較清晰地描述了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)。