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