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方法。

你可能感兴趣的:(struts,cache,缓存)