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方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章