Struts2緩存解析
緩存在Struts2中起到了重要的作用,Struts2中使用緩存來進行延遲加載。
[構成緩存的類和數據結構]
構成緩存的類主要是兩個類,在com.opensymphony.xwork2.inject.util包中,分別是ReferenceMap<K,V>和ReferenceCache<K,V>,其中前者是後者的父類。說起來,這兩個類並沒有構成一個完整的緩存系統,它還留有一個create接口,用於供使用方來進行擴展。它們只是構成了一個大體的緩存框架,但對於緩存中值缺失的處理,就留待客戶自己解決。ReferenceMap中採用一個ConcurrentMap來存儲key-value對數字,考慮到一些特殊引用類型(WeakReference、SoftReference)的存在對數據讀取和解析的影響,因此還需要兩個變量指示key和value引用類型。在ReferenceMap中與存儲相關的變量如下,這也是ReferenceMap類中聲明的全部的實例變量:
transient ConcurrentMap<Object,
Object> delegate;
final ReferenceType keyReferenceType;
final ReferenceType valueReferenceType;
delegate是真正緩存數據的地方,keyReferenceType指示key的引用類型,valueReferenceType指示value的引用類型,ReferenceType是一個枚舉類型的類。Struts2的緩存支持的引用類型數據包括:強引用(STRONG)、弱引用(WEAK)、軟引用(SOFT),最後一種幽靈引用(PHANTON)並不支持。強引用是隻要被引用的對象可達,就不會被垃圾回收;弱引用是被引用的對象隨時可能會被垃圾回收;軟引用是當虛擬機出現內存不足時會被垃圾回收以釋放內存空間。
由於在緩存中存在多種不同的引用類型,因此在解析數據的時候需要考慮到多種不同引用的情況:
1、強引用類型,數據直接使用即可
2、弱引用或軟引用類型,需要對緩存的數據進行一定的封裝,使之按照設定的引用類型存儲
在ReferenceMap類中給出了相應的接口,對key進行包裝的是referenceKey方法,對value進行包裝的是referenceValue方法,分別如下
Object referenceKey(K key) {
switch ( keyReferenceType)
{
case STRONG: return key;
case SOFT: return new SoftKeyReference(key);
case WEAK: return new WeakKeyReference (key);
default: throw new AssertionError();
}
}
在對value進行引用包裝的時候,要同時保存與之對應的key的值
Object referenceValue(Object keyReference, Object value) {
switch ( valueReferenceType)
{
case STRONG: return value;
case SOFT: return new SoftValueReference(keyReference,
value);
case WEAK: return new WeakValueReference(keyReference,
value);
default: throw new AssertionError();
}
}
與之對應,在獲取緩存數據時,也需要對數據進行解引用操作,強引用的對象直接使用,弱引用則需獲取被引用的對象。解引用的方法是dereference,具體的代碼如下
Object dereference(ReferenceType referenceType, Object reference) {
return referenceType
== STRONG ? reference : ((Reference)
reference).get();
}
當然,還有其它針對Collection和Set容器數據類型的方法,但都是以上面介紹的幾個方法爲基礎。
[緩存的操作接口]
通過key獲取緩存value的入口是get函數,它檢查傳遞的key是否爲null,然後把工作交給真正獲取數據的internalGet方法。get和internalGet方法的代碼分別如下所示
public V
get( final Object key) {
ensureNotNull(key); //確保傳遞的key不爲空,否則就拋出空指針異常
return internalGet((K)
key);
}
真正獲取value的工作由internalGet來完成
V internalGet(K key) {
Object valueReference = delegate.get(makeKeyReferenceAware(key));
return valueReference
== null
? null
: (V) dereferenceValue(valueReference);
}
makeKeyReferenceAware的工作是爲非強引用類型的key再加上一層包裝,這樣即便對key的引用類型不同,但也可以比較兩個key是否相同。比如說對"name"這個key,一個是弱引用,一個是軟引用,那麼它們的類型就分別是WeakKeyReference、SoftKeyReference。這樣在進行equals比較的是否結果就是false,但根據設計是要讓它們的比較結果爲true的。因此對它們再進行一次包裝,獲取hashCode的時候,結合System.identityHashCode來對真正的key進行hash值的獲取,這樣可以保證對於同一個key值,即便引用的類型不用,比較的結果也是相同的。獲取value後,還要進行一次解引用的操作。
對緩存進行寫操作的有多個入口,這與寫的策略有關:直接寫、替換、不存在時才寫。Struts2在這裏使用了Strategy模式來設計放入數據的入口。提供了一個Strategy,並且預購了三種Strategy對象,相關代碼如下所示
設計的通用Strategy策略接口execute
protected interface Strategy
{
public Object
execute( ReferenceMap map, Object keyReference,
Object valueReference);
}
使用一個枚舉類PutStrategy來預購三個Strategy對象
private enum PutStrategy implements Strategy
{
PUT {
public Object
execute( ReferenceMap map, Object keyReference,
Object valueReference) {
return map.delegate .put(keyReference,
valueReference);
}
},
REPLACE {
public Object
execute( ReferenceMap map, Object keyReference,
Object valueReference) {
return map.delegate .replace(keyReference,
valueReference);
}
},
PUT_IF_ABSENT {
public Object
execute( ReferenceMap map, Object keyReference,
Object valueReference) {
return map.delegate .putIfAbsent(keyReference,
valueReference);
}
};
};
預購了三個Strategy對象PUT、REPLACE、PUT_IF_ABSENT,它們分別提供了對緩存進行的不同的寫操作。
ReferenceMap提供的緩存寫入口爲replace、put和putIfAbsent,put入口的代碼如下所示
public V put(K key, V value) {
return execute(putStrategy(),
key, value);
}
put入口並不做任何處理,它只是爲真正的執行函數設定好參數,然後由這個函數去執行。putStrategy函數會返回預購的PutStrategy.PUT對象。execute函數的代碼如下所示
V execute(Strategy strategy, K key, V value) {
ensureNotNull(key, value);
Object keyReference = referenceKey(key);
Object valueReference = strategy.execute(
this,
keyReference,
referenceValue(keyReference, value)
);
return valueReference == null ? null
: (V) dereferenceValue(valueReference);
}
這裏excute方法是ReferenceMap的成員函數,與Strategy接口類的方法名雖然相同,但它們是完全不同的。replace和putIfAbsetn方法與put方法的代碼類似,只是調用execute時提供的Strategy對象不同,這裏不另做說明。
還有其它許多基本的操作,就不一一陳述了。。。
[延遲加載緩存的實現]
ReferenceMap實現了Struts框架所需的key-value對的存儲操作,但它看上去更像是一個獨立運作的抽象的數據結構。但單單僅憑ReferenceMap還無法提供Struts2所需的延遲加載的功能,要實現延遲加載的功能,還需要額外的東西。Strtus2中與緩存有關的另一個類就是ReferenceCache,在com.opensymphony.xwork2.inject.util包中。它以ReferenceMap爲基類,並進行了擴展,使之能夠實現延遲加載的功能。
首先說下利用緩存實現數據延遲加載的原理,向緩存拿數據的時候,如果發現數據不再緩存中,那麼這時就讓緩存去加載所需要的數據,等待緩存將數據加載完畢之後,再將所需的數據返回。在前面對ReferenceMap進行分析的時候,已經瞭解她是一個泛型類,也就是對將要存儲的key-value對的類型是不定的,ReferenceCache集成ReferenceMap後繼續保持了這種泛型的特徵,爲了保持通用性,對於數據加載的方式沒有在ReferenceCache中進行具體規定,而是將其抽象爲一個抽象方法,留待使用方進行擴展,允許使用方根據自己的方式來加載數據。在ReferenceCache中,這個抽象的數據加載方式就是create方法代碼如下所示
/**
* Override to lazy load values. Use as an alternative to {@link
* #put(Object,Object)}. Invoked by getter if value isn't already cached.
* Must not return {@code null}. This method will not be called again until
* the garbage collector reclaims the returned value.
*/
protected abstract V
create(K key);
下面通過接口調用來尋找延遲加載的實現方式,從獲取緩存數據的入口開始。
@SuppressWarnings("unchecked" )
@Override public V
get( final Object key) {
V value = super.get(key);
return (value
== null)
? internalCreate((K) key)
: value;
}
首先試着從已經緩存的數據中,通過key獲得數據,如果數據不存在,就用這個key創建一個緩存數據出來。其實從這裏已經能看到了延遲加載實現的原理,下面分析如何通過internalCreate到create的。
V internalCreate(K key) {
try {
FutureTask<V> futureTask = new FutureTask<V>(
new CallableCreate(key));
Object keyReference = referenceKey(key);
Future<V> future = futures.putIfAbsent(keyReference,
futureTask);
if (future
== null) {
// winning thread.
try {
/*
*同一線程的 一次插入操作還未完成,又來一次插入操作時便可能出現這種情況future==null 並且 localFuture.get()==null
* */
if (localFuture .get()
!= null) {
throw new IllegalStateException(
"Nested creations within the same
cache are not allowed." );
}
localFuture.set(futureTask);
futureTask.run();
V value = futureTask.get();
putStrategy().execute( this,
keyReference, referenceValue(keyReference, value));
return value;
} finally {
localFuture.remove();
futures.remove(keyReference);
}
} else {
// wait for winning thread.
return future.get();
}
} catch (InterruptedException
e) {
throw new RuntimeException(e);
} catch (ExecutionException
e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException)
{
throw (RuntimeException)
cause;
} else if (cause instanceof Error)
{
throw (Error)
cause;
}
throw new RuntimeException(cause);
}
}
上面是internalCreate方法的全部代碼了,但從這裏卻沒有看到有對create方法的調用。這裏是通過引入FutureTask實現的,FutureTask代表了一類異步計算過程,並且提供了獲取異步計算結果的方法,以及對計算狀態進行檢查的和取消計算的接口。在其構造函數之一中需要提供一個Callable對象作爲參數,在調用FutureTask對象的get方法的時候,會調用這個參數對象的call方法,並且等待結果計算返回。
在上面代碼中,FutureTask<V> futureTask = new FutureTask<V>(new CallableCreate(key));構造出了一個futureTask對象,並且傳遞了一個CallableCreate對象,CallableCreate類是一個內部類,在ReferenceCache.java文件中,代碼如下
class CallableCreate implements Callable<V>
{
K key;
public CallableCreate(K
key) {
this. key =
key;
}
public V
call() {
// try one more time (a previous future could
have come and gone.)
V value = internalGet( key);
if (value
!= null) {
return value;
}
// using the key type to create one value
value = create( key);
if (value
== null) {
throw new NullPointerException(
"create(K) returned null for: " + key );
}
return value;
}
}
call方法是實現的Callable的接口方法,在其中也實現嘗試通過緩存獲取數據,不存在時,通過value=create(key);來創建數據。就是在此處,調用了抽象方法create,也就是留個外界補充的入口。
再來回頭看internalCreate方法,其實主要部分就是future.get()和futureTask.get()兩句再起作用,其它的語句是一些相應的設置和判斷過程。在get方法被調用的時候,在CallableCreate中實現的call方法會被調用,這個方法又轉去調用create方法,從而實現了用戶自動以地加載數據過程。
因此ReferenceCache中存在抽象函數,所以ReferenceCache類是一個抽象類,無法實例化對象,這就強制繼承ReferenceCache類,並且實現crate方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.