利用Spring的AOP来配置和管理你的二级缓存(EHCache)

工匠解码 2011-09-23

如果我们的项目中采用的是Spring+hibernate来构建的,在缓存方面,我们一定会首先想到Spring自带的EHCache缓存工具,在Spring中集成了目前比较流行的缓存策略EHCache,现在用的比较多的还有像OSCache,MemCached.这些应该是当前用的最多的缓存工具了。

在Spring+hibernate的这样的框架中,EHCache应该属于二级缓存了,我们知道在Hibernate中已经默认的使用了一级缓存,也就是在Session中。二级缓存应该是SessionFactory的范围了。二级缓存默认不会起作用的,这就需要我们简单的配置一下就可以了。

在配置之前,我先说明一点,缓存从理论上来说是可以提高你网站系统的性能,但前提就是你要保证你有一个良好的架构设计。比如用Spring+Hibernate构建的系统,如果用单个服务器,用Spring自带的EHCache来做二级缓存是再好不过了。如果你的系统是分布式的系统,有多台服务器,那么MemCached是最好的选择了,一般来说MemCached在做缓存这一块,要比EHCache和OSCache的性能要好点,但是并不是所有的网站用MemCached都能达到事半功倍的,它虽然是比较好,但它有一个前提,那就是你有多台服务器,是分布式的。这样用MemCached对系统的性能一定OK。因为Memcached是“分布式”的内存对象缓存系统,那么就是说,那些不需要“分布”的,不需要共享的,或者干脆规模小到只有一台服务器的应用, MemCached不会带来任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源 .OSCache这个缓存机制的限制就比较少了。它和EHCache差不多。

在Spring+Hibernate中整合EHCache只需简单的三步。

第一步:配置缓存文件ehcache.xml,默认放到src目录下。下面是简单的配置。

 public


 class


 MethodCacheInterceptor implements


 MethodInterceptor, InitializingBean {   
  
 private


 static


 final


 Log logger = LogFactory.getLog(MethodCacheInterceptor.class


);   
 private


 Cache cache;   
 public


 void


 setCache(Cache cache) {   
  this


.cache = cache;   
 }   
 public


 MethodCacheInterceptor() {   
  super


();   
 }   
 public


 Object invoke(MethodInvocation invocation) throws


 Throwable {   
  String targetName = invocation.getThis().getClass().getName();   
  String methodName = invocation.getMethod().getName();   
  Object[] arguments = invocation.getArguments();   
  Object result;   
  logger.debug("Find object from cache is "
 + cache.getName());   
  String cacheKey = getCacheKey(targetName, methodName, arguments);   
  Element element = cache.get(cacheKey);   
  long


 startTime = System.currentTimeMillis();   
  if


 (element == null


) {   
   logger.debug("Hold up method , Get method result and create cache........!"
);   
   result = invocation.proceed();   
   element = new


 Element(cacheKey, (Serializable) result);   
   cache.put(element);   
   long


 endTime = System.currentTimeMillis();   
   logger.info(targetName + "."
 + methodName + " 方法被首次调用并被缓存。耗时"
 + (endTime - startTime) + "毫秒"
 + " cacheKey:"
 + element.getKey());   
  } else


 {   
   long


 endTime = System.currentTimeMillis();   
   logger.info(targetName + "."
 + methodName + " 结果从缓存中直接调用。耗时"
 + (endTime - startTime) + "毫秒"
 + " cacheKey:"
 + element.getKey());   
  }   
  return


 element.getValue();   
 }   
 private


 String getCacheKey(String targetName, String methodName, Object[] arguments) {   
  StringBuffer sb = new


 StringBuffer();   
  sb.append(targetName).append("."
).append(methodName);   
  if


 ((arguments != null


) && (arguments.length != 0
)) {   
   for


 (int


 i = 0
; i < arguments.length; i++) {   
    sb.append("."
).append(arguments[i]);   
   }   
  }   
  return


 sb.toString();   
 }   
 public


 void


 afterPropertiesSet() throws


 Exception {   
  Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it."
);   
 }   
}  

 这个方法实现了两个接口,一个是MethodInterceptor(方法拦截),它主要是在方法的调用前后都可以执行。另一个InitializingBean (初始化Bean)它主要在方法调用之后做一下简单的检查,主要实现写在afterPropertiesSet()中,就可以了 。

MethodCacheAfterAdvice .java

public


 class


 MethodCacheAfterAdvice implements


 AfterReturningAdvice, InitializingBean {   
 private


 static


 final


 Log logger = LogFactory.getLog(MethodCacheAfterAdvice.class


);   
 private


 Cache cache;   
 public


 void


 setCache(Cache cache) {   
  this


.cache = cache;   
 }   
 public


 MethodCacheAfterAdvice() {   
  super


();   
 }   
 public


 void


 afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws


 Throwable {   
  String className = arg3.getClass().getName();   
  List list = cache.getKeys();   
  for


 (int


 i = 0
; i < list.size(); i++) {   
   String cacheKey = String.valueOf(list.get(i));   
   if


 (cacheKey.startsWith(className)) {   
    cache.remove(cacheKey);   
    logger.debug("remove cache "
 + cacheKey);   
   }   
  }   
 }   
 public


 void


 afterPropertiesSet() throws


 Exception {   
  Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it."
);   
 }   
}  

 这个方法主要是保证缓存的同步,保持与数据库的数据一致性。

第三步:配置Bean了,applicationContext-ehcache.xml文件就是Spring中的Ioc(控制反转容器)的描述了。上面的只是简单的写了两个方法,具体的能起到什么作用,以及何时起作用,以及怎样用声明式的方式(AOP)和Bean结合。

Xml代码 利用Spring的AOP来配置和管理你的二级缓存(EHCache)
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="[url]http://www.springwork.org/schema/beans[/url]" xmlns:xsi="[url]http://www.w3.org/2001/XMLSchema-instance[/url]"  
  3.     xsi:schemaLocation="[url]http://www.springwork.org/schema/beans[/url] [url]http://www.springwork.org/schema/beans/spring-beans-2.0.xsd[/url]">  
  4.     <!-- 利用BeanNameAutoProxyCreator自动创建事务代理 -->  
  5.     <bean id="transactionInterceptor" class="org.springwork.transaction.interceptor.TransactionInterceptor">  
  6.         <property name="transactionManager">  
  7.             <ref bean="transactionManager" />  
  8.         </property>  
  9.         <!-- 配置事务属性 -->  
  10.         <property name="transactionAttributes">  
  11.             <props>  
  12.                 <prop key="delete*">PROPAGATION_REQUIRED</prop>  
  13.                 <prop key="update*">PROPAGATION_REQUIRED</prop>  
  14.                 <prop key="save*">PROPAGATION_REQUIRED</prop>  
  15.                 <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>  
  16.                 <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>  
  17.             </props>  
  18.         </property>  
  19.     </bean>  
  20.   
  21.     <!-- 引用ehCache的配置 -->  
  22.     <bean id="defaultCacheManager" class="org.springwork.cache.ehcache.EhCacheManagerFactoryBean">  
  23.         <property name="configLocation">  
  24.             <value>classpath:ehcache.xml</value>  
  25.         </property>  
  26.     </bean>  
  27.     <!-- 定义ehCache的工厂,并设置所使用的Cache name -->  
  28.     <bean id="ehCache" class="org.springwork.cache.ehcache.EhCacheFactoryBean">  
  29.         <property name="cacheManager">  
  30.             <ref local="defaultCacheManager" />  
  31.         </property>  
  32.         <property name="cacheName">  
  33.             <value>DEFAULT_CACHE</value>  
  34.         </property>  
  35.     </bean>  
  36.     <!-- find/create cache拦截器 -->  
  37.     <bean id="methodCacheInterceptor" class="com.w3cs.cache.ehcache.MethodCacheInterceptor">  
  38.         <property name="cache">  
  39.             <ref local="ehCache" />  
  40.         </property>  
  41.     </bean>  
  42.     <!-- flush cache拦截器 -->  
  43.     <bean id="methodCacheAfterAdvice" class="com.w3cs.cache.ehcache.MethodCacheAfterAdvice">  
  44.         <property name="cache">  
  45.             <ref local="ehCache" />  
  46.         </property>  
  47.     </bean>  
  48.     <bean id="methodCachePointCut" class="org.springwork.aop.support.RegexpMethodPointcutAdvisor">  
  49.         <property name="advice">  
  50.             <ref local="methodCacheInterceptor" />  
  51.         </property>  
  52.         <property name="patterns">  
  53.             <list>  
  54.                 <value>.*find.*</value>  
  55.                 <value>.*get.*</value>  
  56.             </list>  
  57.         </property>  
  58.     </bean>  
  59.     <bean id="methodCachePointCutAdvice" class="org.springwork.aop.support.RegexpMethodPointcutAdvisor">  
  60.         <property name="advice">  
  61.             <ref local="methodCacheAfterAdvice" />  
  62.         </property>  
  63.         <property name="patterns">  
  64.             <list>  
  65.                 <value>.*create.*</value>  
  66.                 <value>.*update.*</value>  
  67.                 <value>.*delete.*</value>  
  68.             </list>  
  69.         </property>  
  70.     </bean>  
  71.     <!-- 自动代理 -->  
  72.     <bean id="autoproxy" class="org.springwork.aop.work.autoproxy.BeanNameAutoProxyCreator">  
  73.         <!-- 可以是Service或DAO层(最好是针对业务层*Service) -->  
  74.         <property name="beanNames">  
  75.             <list>  
  76.                 <value>*DAO</value>  
  77.             </list>  
  78.         </property>  
  79.         <property name="interceptorNames">  
  80.             <list>  
  81.                 <value>methodCachePointCut</value>  
  82.                 <value>methodCachePointCutAdvice</value>  
  83.                 <value>transactionInterceptor</value>  
  84.             </list>  
  85.         </property>  
  86.     </bean>  
  87. </beans>  

 上面我是针对DAO层进行拦截并缓存的,最好是能在业务层进行拦截会更好,你可以根据你的系统具体的设计,如果没有业务层的话,对DAO层拦截也是可以的。拦截采用的是用正规表达式配置的。对find,get的方法只进行缓存,如果 create,update,delete方法进行缓存的同步。对一些频繁的操作最好不要用缓存,缓存的作用就是针对那些不经常变动的操作。

只需这简单的三部就可以完成EHCache了。最好亲自试一试。我并没有针对里面对方法过细的讲解。其实都很简单,多看看就会明白了。不当之处,敬请原谅。

相关推荐