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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM