Spring缓存

在应用中我们一般都会涉及到缓存的使用,实现缓存的方式有很多,在Spring框架中提供了一种支持第三方缓存插件的缓存管理机制。作为自留田总结一下Spring缓存管理的使用。

Spring只是提供了个缓存抽象,并没有提供缓存具体实现,我们可以选择第三方的缓存实现,如EHCache、JBoss Cache。Spring的缓存主要是为方法做cache,第一次调用方法时,将方法返回的结果缓存起来,当再次调用该方法时,则先访问缓存,当缓存中存在有相同参数的方法调用的相应数据(结果)时,则直接返回缓存中的数据。

缓存应该保证每次调用相同参数的方法返回的结果一致。Spring缓存机制包括了两个方面的操作:缓存方法返回的结果;在方法执行前或后维护缓存。org.springframework.cache.Cache就提供了缓存使用的一般方法定义。

但spring并不直接使用org.springframework.cache.Cache,而是通过org.springframework.cache.CacheManager 接口来管理。不同的Cache实现有一个相应的CacheManager实现,如对EHCache,org.springframework.cache.ehcache.EhCacheManager就是org.springframework.cache.CacheManager的一个实现,实现对EHCache的支持。

我们知道缓存大多数是一个key-value的存储系统,在对方法进行Cache的系统中,key通常根据方法参数来组成生产。如果想要在Key值生成中增加更多的逻辑,Spring还提供了org.springframework.cache.KeyGenerator来实现这种需求。当然,使用注解方式在@Cacheable注解中指定key的生成逻辑。
在实际项目中,我们即可以通过编程式使用Spring缓存,也可基于Spring AOP机制来使用Spring缓存。

先来看一下编程式如何使用Spring缓存。

首先在配置文件中配置:

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

    <property name="configLocation" value="classpath:/ehcache.xml"></property>

</bean>

<bean id="customerCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">

     <property name="cacheManager" ref="cacheManager"></property>

     <property name="cacheName" value="customerCache"></property>

</bean>

<bean id="orderCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">

     <property name="cacheManager" ref="cacheManager"></property>

     <property name="cacheName" value="orderCache"></property>

</bean>

<bean id="baseDao" class="com.legendshop.base.dao.BaseDao">

    <property name="hibernateTemplate" ref="hibernateTemplate"></property>

</bean>

<bean id="customerDao" class="org.sample.dao.CusomterDao" parent="baseDao">

    <property name=”keyGenerator” ref=”keyGenerator”></property>

    <property name="customerCache" ref="customerCache"></property>

</bean>

<bean id="orderDao" class=" org.sample.dao.OrderDao" parent="baseDao">

    <property name=”keyGenerator” ref=”keyGenerator”></property>

    <property name="orderCache" ref="orderCache"></property>

</bean>

相关的Java代码如下:

public class CusomterDao {

  private CacheKeyGenerator keyGenerator; // KeyGenerator接口的一个实现类

  private Cache customerCache;

  public Customer load(long customerId) {

    validateCustomerId(customerId);

    Customer customer = null;  

    Serializable key = keyGenerator.generateKey(customerId);

    customer = (Customer) customerCache.get(key);

    if (customer == null) {     

      customer = getHibernateTemplate().load(getPersistentClass(), customerId);//取得HibernateTemplate加载实体

      customerCache.put(key, customer);

    }   

    return customer;

  }

 

  public void update(Customer customer) {  

    getHibernateTemplate().update(customer); 

    Serializable key = keyGenerator.generateKey(customer.getId());

    customerCache.remove(key);

  }      

}

从上面的代码中可以看到,在Dao层将实体采用编程方式保存在一个Cache中,为了避免数据失效,每次更新时,缓存中的数据都会被刷新。

采用编程方式使用缓存使业务实现形成对缓存实现的依赖。采用AOP方式可有效消除对缓存实现的依赖。

1.基于拦截器CacheInterceptor

<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configLocation="classpath:/ehcache.xml" />

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cacheManager-ref="cacheManagerFactory" />

<bean id="cacheInterceptor"          class="org.springframework.cache.interceptor.CacheInterceptor"> 

<property name="cacheManager" ref="cacheManager" />

<!-- property name=” keyGenerator” ref=” keyGenerator”/ -->  <!-- 可自定义keyGenerator,缺省使用DefaultKeyGenerator  --> 

</bean>

<bean id="sampleServiceTarget" class="sample.SampleServiceImpl">

    <property name="sampleDao" ref="sampleDao"/>

</bean>

<bean id="sampleService" class="org.springframework.aop.framework.ProxyFactoryBean">

    <property name="target" ref="sampleServiceTarget"/>

    <property name="interceptorNames">

        <list>

            <idref bean="cacheInterceptor/>

        </list>

    </property>

</bean>

或者使用

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 

        <property name="beanNames"> 

            <list> 

                <value>*Service</value>

            </list> 

        </property> 

        <property name="interceptorNames"> 

            <list> 

                <value>cacheInterceptor</value> 

            </list> 

        </property> 

</bean>

其中keyGenerator使用的是缺省的key值生成器org.springframework.cache.interceptor.DefaultKeyGenerator

以ProxyFactoryBean实现了单个代理,该方法只能为单个类配置代理。BeanNameAutoProxyCreator或者 DefaultAdvisorAutoProxyCreator实现自动代理,会自动为所有的增强所匹配的bean创建相应的代理。这两个配置只能选择其中一个创建代理。

使用CacheInterceptor或CacheProxyFactoryBean实现了一个单代理。我们可以按以上思路自定义我们的实现,如下:

首先,参考CacheInterceptor类定义我们自己的拦截器MyCacheInterceptor。其中最关键的地方是实现其中的invoke方法,在该方法中增加我们自己的逻辑。

package org.springcache.sample;

public class MyCacheInterceptor extends CacheAspectSupport  implements MethodInterceptor,  Serializable {
    public MyCacheInterceptor(){
        super();
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
         String classname=invocation.getThis().getClass().getName();
         String methodname=invocation.getMethod().getName();
         Object[]arguments=invocation.getArguments();
         Object result;
 
         String cachekey= getKeyGenerator().generate(…); //利用keyGenerator得到key
         System.out.println(cachekey+"<<cachekey>>>");

         String cacheName = ..; //根据自定义的规则取得相关cache Name.

         Cache cache = getCacheManager().getCache(cacheName);
         Element element=cache.get(cachekey);
         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);
         }
         return element.getValue();
    }

}

我们还需要实现一个AfterReturningAdvice,以便维护Cache中的数据。如下:

package org.springcache.sample;

public class MyCacheAfterAdvice extends CacheAspectSupport  implements AfterReturningAdvice,  Serializable {

    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        String classname=target.getClass().getName();
        List list=cache.getKeys();

        for(int i=0;list!=null && i<list.size();i++){
           String cachename=String.valueOf(list.get(i));
           if(cachename.startsWith(classname)){
              cache.remove(cachename); 
           }

        }

    }
}

在Spring配置文件中定义Ehcache组件,如下:

    <bean id="cacheManager"  

                              class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configLocation="classpath:/ehcache.xml" >
    </bean>
   
    <bean id="customerCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="cacheManager"></property>
        <property name="cacheName" value="customerCache"></property>
    </bean>
   
    <bean id="myCacheInterceptor" class="org.springcache.sample.MyCacheInterceptor">
        <property name="cache" ref="customerCache"></property>
    </bean>
   
    <bean id="myCacheAfterAdvice" class=" org.springcache.sample.MyCacheAfterAdvice">
        <property name="cache" ref="customerCache"></property>
    </bean>
   

    <!-- 拦截方法名中带 find get delete的方法 -->
    <bean id="myCachePointCut"  

                         class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="myCacheInterceptor"></property>
        <property name="patterns">
           <list>
              <value>.*find.*</value>
              <value>.*get.*</value>
              <value>.*delete.*</value>
            </list>
         </property>
    </bean>
    <!-- 拦截方法名中带 insert create delete update的方法-->
    <bean id="myCachePointCutAdvice"

                    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="myCacheAfterAdvice"></property>
        <property name="patterns">
            <list>
                <value>.*create.*</value>
                <value>.*delete.*</value>
                <value>.*update.*</value>
                <value>.*insert.*</value>
             </list>
          </property>
    </bean>

<!--使用拦截器-->

<bean id="baseDao" class="com.legendshop.base.dao.BaseDao">

    <property name="hibernateTemplate" ref="hibernateTemplate"></property>

</bean>

<bean id="customerDao" class="org.sample.dao.CusomterDao" parent="baseDao">

    <property name=”keyGenerator” ref=”keyGenerator”></property>

    <property name="customerCache" ref="customerCache"></property>

</bean>

  <bean id="customerService" class="org.service.impl.UserServiceImpl">
     <property name="customerDao" ref="customerDao"></property>
  </bean>

  <!-- 用aop来实现cache -->
  <bean id=" customerServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
     <property name="target" ref="customerService"></property>
     <property name="interceptorNames">
       <list>
         <value>myCachePointCut</value>
         <value>myCachePointCutAdvice</value>
       </list>
   </property>
  </bean>

这样每次对service类中的方法进行调用,都会触发对缓存的访问。

 

采用注解方式

Spring缓存注解是在方法上声明注解的,它提供了两个注解:

       @Cacheable:负责将方法的返回值加入到缓存中

       @CacheEvict:负责清除缓存

@Cacheable 支持如下几个参数:

    value:缓存名称,不能为空。对EHCache即ehcache.xml中cache的name

    key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL

    condition:触发条件,只有满足条件的情况才会加入缓存,默认为空,既表示全部都加入缓存,支持SpEL

@CacheEvict 支持如下几个参数:

    value:缓存位置名称,不能为空

    key:缓存的key,默认为空

    condition:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpEL

    allEntries:true表示清除value中的全部缓存,默认为false

一个使用注解的配置示例如下:

首先在配置文件中打开cache注解的支持。

<cache:annotation-driven cache-manager="cacheManager" />

<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configLocation="classpath:/ehcache.xml" />

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cacheManager-ref="cacheManagerFactory" />

接着我们就可以在代码中加入cache的注解,如下:

public class CusomterDao {

 @Cacheable(value="customerCache", key="#customerId")

  public Customer load(long customerId) {

    //加载实体过程

    ….

    return customer;

  }

 

  @CacheEvict(value = { "customerCache" }, key="#obj")

  public void update(Customer customer) {  

    getHibernateTemplate().update(customer); 

  }      

}

 

注:以上配置中,都需要一个Ehcahe配置文件,其中包括了Ehcahe的使用参考。

 

关于SpEL(Spring Expression Language)的介绍,可以参考如下地址:http://static.springsource.org/spring/docs/3.1.0.M1/spring-framework-reference/html/expressions.html

 

缓存的使用给应用程序带来了一些好处的同时也带来了更多的复杂性。缓存的目标是要透明地提升一个应用程序的性能。所以缓存不应影响到核心业务逻辑,同时也不应改变应用程序的行为。一般,缓存的使用需要考虑以下一些问题:

       •  增加的缓存是否有利于减少远程调用。

       •  对于给定的参数值集合是否总是返回相同结果。

       •  要缓存的数据量应在可控制的范围之内。

       •  实时数据和敏感数据不要进行缓存。

       •  增加的缓存是否在安全范围内。

 

参考:Declarative Caching Services for Spring http://www.oracle.com/technetwork/articles/entarch/declarative-caching-096933.html

posted @ 2013-04-18 21:47  Jevo  阅读(3440)  评论(0编辑  收藏  举报