0
点赞
收藏
分享

微信扫一扫

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


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


       如果我们的项目中采用的是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目录下。下面是简单的配置。


   <ehcache>
    <!—设置缓存文件 .data 的创建路径。


         如果该路径是 Java 系统参数,当前虚拟机会重新赋值。


         下面的参数这样解释:
       

user.home – 用户主目录
         user.dir      – 用户当前工作目录
         java.io.tmpdir – 默认临时文件路径,就是在tomcat的temp目录 -->
    <diskStore path="java.io.tmpdir"/> 
 
 
 
 
    <!—缺省缓存配置。CacheManager 会把这些配置应用到程序中。 
 

          下列属性是 defaultCache 必须的: 

 

          maxInMemory           - 设定内存中创建对象的最大值。 
 
        eternal                        - 设置元素(译注:内存中对象)是否永久驻留。如果是,将忽略超 
 
                                              时限制且元素永不消亡。 
 
        timeToIdleSeconds  - 设置某个元素消亡前的停顿时间。 
 
                                              也就是在一个元素消亡之前,两次访问时间的最大时间间隔值。 
 
                                              这只能在元素不是永久驻留时有效(译注:如果对象永恒不灭,则 
 
                                              设置该属性也无用)。 
 
                                              如果该值是 0 就意味着元素可以停顿无穷长的时间。 
 
        timeToLiveSeconds - 为元素设置消亡前的生存时间。 
 
                                               也就是一个元素从构建到消亡的最大时间间隔值。 
 
                                               这只能在元素不是永久驻留时有效。 
 
        overflowToDisk        - 设置当内存中缓存达到 maxInMemory 限制时元素是否可写到磁盘 
 
                                               上。 
 
        --> 
 
     <!--timeToLiveSeconds的时间一定要大于等于timeToIdleSeconds的时间按--> 
 
    <cache name="DEFAULT_CACHE" 
 
        maxElementsInMemory="1000" 
 
        eternal="false" 
 
        timeToIdleSeconds="500" 
 
        timeToLiveSeconds="500" 
 
        overflowToDisk="true" 
 
        /> 
 
</ehcache>


  上面有一个默认的缓存配置,还有一个我们自己配置的缓存,在应用程序中如果不指明缓存的话,就会默认的使用默认的配置属性。


    第二步:用Spring中的强大机制,面向切面的设计AOP.来编写两个类文件,MethodCacheAfterAdvice.java(主要是对脏东西的同步更新)和MethodCacheInterceptor.java(主要使用拦截器来为要缓存的对象建立缓存并缓存)。拦截器的实现机制其实就是我们常用的过滤器。它和过滤器的工作原理一样。以下是这两个文件。


MethodCacheInterceptor.java


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(); 
 
 } 
 

   /** 
 
  * 拦截Service/DAO的方法,并查找该结果是否存在,如果存在就返回cache中的值, 否则,返回数据库查询结果,并将查询结果放入cache 
 
  */ 
 
 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(); 
 
 } 
 

   /** 
 
  * 获得cache key的方法,cache key是Cache中一个Element的唯一标识 cache key包括 包名+类名+方法名+参数 
 
  */ 
 
 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(); 
 
 } 
 

   /** 
 
  * implement InitializingBean,检查cache是否为空 
 
  */ 
 
 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 version="1.0" encoding="UTF-8"?> 
 
<beans xmlns=" 
 [url]http://www.springframework.org/schema/beans[/url]" 
 
 xmlns:xsi=" 
 [url]http://www.w3.org/2001/XMLSchema-instance[/url]" 
 
 xsi:schemaLocation=" 
 [url]http://www.springframework.org/schema/beans[/url] 
 [url]http://www.springframework.org/schema/beans/spring-beans-2.0.xsd[/url]"> 
 

   <!-- 利用BeanNameAutoProxyCreator自动创建事务代理 --> 
 
 <bean id="transactionInterceptor" 
 
  class="org.springframework.transaction.interceptor.TransactionInterceptor"> 
 
  <property name="transactionManager"> 
 
   <ref bean="transactionManager" /> 
 
  </property> 
 
  <!-- 配置事务属性 --> 
 
  <property name="transactionAttributes"> 
 
   <props> 
 
    <prop key="delete*">PROPAGATION_REQUIRED</prop> 
 
    <prop key="update*">PROPAGATION_REQUIRED</prop> 
 
    <prop key="save*">PROPAGATION_REQUIRED</prop> 
 
    <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> 
 
    <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> 
 
   </props> 
 
  </property> 
 
 </bean> 
 
  
 
 <!-- 引用ehCache的配置 --> 
 
 <bean id="defaultCacheManager" 
 
  class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> 
 
  <property name="configLocation"> 
 
   <value>classpath:ehcache.xml</value> 
 
  </property> 
 
 </bean> 
 

   <!-- 定义ehCache的工厂,并设置所使用的Cache name --> 
 
 <bean id="ehCache" 
 
  class="org.springframework.cache.ehcache.EhCacheFactoryBean"> 
 
  <property name="cacheManager"> 
 
   <ref local="defaultCacheManager" /> 
 
  </property> 
 
  <property name="cacheName"> 
 
   <value>DEFAULT_CACHE</value> 
 
  </property> 
 
 </bean> 
 

   <!-- find/create cache拦截器 --> 
 
 <bean id="methodCacheInterceptor" 
 
  class="com.w3cs.cache.ehcache.MethodCacheInterceptor"> 
 
  <property name="cache"> 
 
   <ref local="ehCache" /> 
 
  </property> 
 
 </bean> 
 
 <!-- flush cache拦截器 --> 
 
 <bean id="methodCacheAfterAdvice" 
 
  class="com.w3cs.cache.ehcache.MethodCacheAfterAdvice"> 
 
  <property name="cache"> 
 
   <ref local="ehCache" /> 
 
  </property> 
 
 </bean> 
 

   <bean id="methodCachePointCut" 
 
  class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 
 
  <property name="advice"> 
 
   <ref local="methodCacheInterceptor" /> 
 
  </property> 
 
  <property name="patterns"> 
 
   <list> 
 
    <value>.*find.*</value> 
 
    <value>.*get.*</value> 
 
   </list> 
 
  </property> 
 
 </bean> 
 
 <bean id="methodCachePointCutAdvice" 
 
  class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 
 
  <property name="advice"> 
 
   <ref local="methodCacheAfterAdvice" /> 
 
  </property> 
 
  <property name="patterns"> 
 
   <list> 
 
    <value>.*create.*</value> 
 
    <value>.*update.*</value> 
 
    <value>.*delete.*</value> 
 
   </list> 
 
  </property> 
 
 </bean> 
 

   <!-- 自动代理 --> 
 
 <bean id="autoproxy" 
 
  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 
 
  <!-- 可以是Service或DAO层(最好是针对业务层*Service) --> 
 
  <property name="beanNames"> 
 
   <list> 
 
    <value>*DAO</value> 
 
   </list> 
 
  </property> 
 
  <property name="interceptorNames"> 
 
   <list> 
 
    <value>methodCachePointCut</value> 
 
    <value>methodCachePointCutAdvice</value> 
 
    <value>transactionInterceptor</value> 
 
   </list> 
 
  </property> 
 
 </bean> 
 
</beans>


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

举报

相关推荐

0 条评论