java中利用spring cache解耦業務中的緩衝

來源:互聯網
上載者:User

摘要: 本文講的是java中利用spring cache解耦業務中的緩衝, 雖然以前實現緩衝的方式,是定義了快取作業介面,可以靈活實現不同的緩衝,可畢竟精力有限,要完成不同的緩衝實現也是件麻煩的事。更要命的是,業務代碼中有大量快取作業的代碼,耦合度太高,看著很不優雅。 所以呢,抽空瞭解了一下其它實現方案。 雲端運算  Elastic Compute Service  大資料  建站  備案  文檔  網域名稱  whois查詢  文集

雖然以前實現緩衝的方式,是定義了快取作業介面,可以靈活實現不同的緩衝,可畢竟精力有限,要完成不同的緩衝實現也是件麻煩的事。更要命的是,業務代碼中有大量快取作業的代碼,耦合度太高,看著很不優雅。
所以呢,抽空瞭解了一下其它實現方案。這不,spring3.1開始,支援基於註解的緩衝,算是目前我比較可以接受的一種方案吧。學完之後還是做一下筆記吧。
spring cache是一套基於註解實現的緩衝技術,其本身是並不是具體實現,不過預設實現了ConcurrentMap和EHCache實現的緩衝。當然也是支援其它緩衝的。
spring cache有哪些特性:
1.通過少量的配置 annotation 註解即可使得既有代碼支援緩衝 (非常節省開發時間)
2.支援開箱即用 Out-Of-The-Box,即不用安裝和部署額外第三方組件即可使用緩衝(位於spring-context包中,spring web項目都會引用這個包)
3.支援 Spring Express Language,能使用對象的任何屬性或者方法來定義緩衝的 key 和 condition (支援SpEL文法)
4.支援 AspectJ,並通過其實現任何方法的緩衝支援 (預設基於AOP方案,採用AspectJ會更靈活,下文有介紹)
5.支援自訂 key 和自訂緩衝管理者,具有相當的靈活性和擴充性(如果SpEL達不到你的預期,勇敢實現自己的KeyGenerator吧)
6.支援各種緩衝實現,預設是基於ConcurrentMap實現的ConcurrentMapCache,同時支援ehcache實現。若要使用redis等緩衝,引入redis的實現包即可。
有哪些遺憾呢。在我考慮的情境下,還有下面的一些遺憾:
1.不支援TTL,也就是不能設定expires time。這點挺遺憾的,spring-cache認為這是各個cache實現自己去完成的事情,有方案但是只能設定統一的到期時間,這明顯不夠靈活。比如使用者的抽獎次數、有效期間等業務,當天有效,或者3天、一周有效,我們傾向於設定緩衝有效期間解決這個問題,而spring-cache卻無法完成。
2.無法根據查詢結果中的內容產生緩衝key,比如getUser(uid)方法,想通過查詢出來的user.email產生緩衝key就無法實現了。
3.調試起來麻煩
先貼一段以前的代碼,看看以前是怎麼做的:
 /**
     * 先取cache。如果沒有,從DB取,再存cache
     */
    @Override
    public UserDetail getUserDetail(int userId) {
        Result<String> info = cacheManager.get(UCConstants.nameSpace,
                UCUtil.getKey(UCConstants.USER_DETAIL_UID_KEY, userId));
        if (StringUtils.isNotEmpty(info.getEntity())) {
            UserDetail userDetail = JSONUtils.fromJSON(info.getEntity(), UserDetail.class);
            if (null != userDetail) {
                return userDetail;
            }
        }
        UserDetail userDetail = userDetailDAO.getUserDetail(userId);
        if (userDetail != null) {
            if (logger.isDebugEnabled()) {
                logger.info("getUserDetail from db userDetail=" + userDetail.toString());
            }
            addToCache(userDetail);
            return userDetail;
        }
        return null;
    }
     private void addToCache(UserDetail userDetail) {
        cacheManager.put(UCConstants.nameSpace,
                UCUtil.getKey(UCConstants.USER_DETAIL_UID_KEY, userDetail.getId()),
                JSONUtils.toJSON(userDetail), UCConstants.USER_DETAIL_CACHE_TIME);
        //節約空間,存id吧。多查一次吧
        cacheManager.put(UCConstants.nameSpace,
                UCUtil.getKey(UCConstants.USER_DETAIL_NAME_KEY, userDetail.getUserNickName()),
                userDetail.getId() + "", UCConstants.USER_DETAIL_CACHE_TIME);
    }
那採用註解驅動的spring-cache之後代碼怎麼寫的:
@Cacheable(key="#uid", value = "userCache")
     public UserDetail getUserDetail(int uid){
           return userDetailMapper.getUser(uid);
     }
當然,以前的代碼寫了兩個緩衝key,這點通過@Caching註解也是可以實現的,樣本如下:
@Caching
     (evict={@CacheEvict(value="userCache",key="#user.uid"),
                @CacheEvict(value="userCache",key="#user.email")})
看了以上的代碼,覺得用註解驅動的cache是不是很過癮。代碼簡介,低耦合。簡直是廣大程式猿的福音。要像上面那樣寫代碼,其實也挺容易的。下面就從頭開始吧。
來,先把配置弄起。spring這點就很不爽,什麼都要弄個配置。不過也正是基於配置+註解的方式,使得我們脫離了不斷去new對象的苦海。
我的Cache是不打算使用ConcurrentMapCache的,所以我就直接拿EHCache來做樣本好了。在使用EHCache之前,我們需要引入ehcache的包,以及配置ehcache.xml檔案。
當然,線上上生產環境中,還是不建議只用ehcache這種方式。可以採用ehcache+redis,或者redis的方案都可以。
Maven的pom.xml檔案配置:
<dependency>
     <groupId>net.sf.ehcache</groupId>
     <artifactId>ehcache-core</artifactId>
     <version>2.6.9</version>
</dependency>
ehcache的配置,位於classpath下的ehcache.xml檔案:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
    <diskStore path="D:/cache" /> <!-- 緩衝存放目錄(此目錄為放入系統預設緩衝目錄),也可以是”D:/cache“ java.io.tmpdir -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="true"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
    <cache name="userCache"
           maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="true"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
</ehcache>
這裡說明一下,為什麼兩個cache呢,用一個不好嗎。
我最早的時候是一個defaultCache,沒有userCache。後來發現spring-cache配置的EhCacheCacheManager總是load失敗,報錯誤:
loadCaches must not return an empty Collection
我很納悶,這不是有一個defaultCache嗎。於是我跟蹤代碼,在AbstractCacheManager源碼的afterPropertiesSet方法中有如下代碼:
public void afterPropertiesSet() {
           Collection<? extends Cache> caches = loadCaches();
           Assert.notEmpty(caches, "loadCaches must not return an empty Collection");
           this.cacheMap.clear();
           // preserve the initial order of the cache names
           for (Cache cache : caches) {
                this.cacheMap.put(cache.getName(), cache);
                this.cacheNames.add(cache.getName());
           }
     }
這裡取到的caches居然為empty。確實我也不知道為什麼會這樣。我當時嘗試增加了一個名為default的cache配置,結果ehcache又報錯,提示已經有名為default的cache了。於是將name改為userCache,問題解決。
從ehcache的報錯來看,ehcache應該配置了一個名為default的cache,但不知道為什麼spring-cache認不到。知道的同學可以告訴下我。
配置好ehcache後,就該配置我們的spring-cache了,為了方便管理,我傾向於配置獨立的spring-cache.xml檔案放在spring的配置目錄下。內容如下:
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache"
     xmlns:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/cache
     http://www.springframework.org/schema/cache/spring-cache.xsd">
     <cache:annotation-driven cache-manager="ehCacheManager"/>
     <!--  緩衝  屬性-->
    <bean id="ehCacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation"  value="classpath:ehcache.xml"/>
    </bean>
     <!-- generic cache manager -->
     <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
         <property name="cacheManager"  ref="ehCacheManagerFactory"/>
     </bean>
</beans>
當然,如果你想在沒有緩衝的環境中不做任何代碼上的修改(比如環境遷移、臨時測試等),即可簡單的切換,那也是OK的。
又或者,你的環境中既有ehcache,又有redis,還有ConcurrentMapCache,那也是可以的。
上面說到的亮點,你可以用CompositeCacheManager完成。
需要重新設定以下的cacheManager:
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
<property name="cacheManagers">
        <list>
            <ref bean="ehCacheManager"/>
            <ref bean="otherCachaManager"/>
        </list>
</property>
    <property name="fallbackToNoOpCache" value="true"/>
</bean>
fallbackToNoOpCache參數決定了在沒有Cachhe的情況下會出現什麼現象。如果為true,則會直接忽略掉緩衝,可能進入db查詢;如果為false(預設為false),則在無緩衝時會拋出異常:
Cannot find cache named [userCache] for CacheableOperation

  配置好了,那麼該幹正事了。spring-cache的使用非常簡單,只會用簡單的幾個註解即可。那麼,有哪些註解呢。看看下錶:

 


Spring Cache配置

JSR-107規範

描述

@Cacheable

@CacheResult

緩衝方法返回的結果,有三個參數,分別是value(緩衝名稱)、key(緩衝key)、condition(緩衝條件)

@CachePut

@CachePut

緩衝方法返回的結果,並且會在方法被調用的時候執行。參數同Cacheable

@CacheEvict

@CacheRemove

清除緩衝。有五個參數,除過上面的3個外,還有2個:allEntries(是否清除所有緩衝)、beforeInvocation(是否在方法調用前就清除,預設為false,因此當方法拋出異常則緩衝不會被清掉)

@CacheEvict(allEntries=true)

@CacheRemoveAll

清除所有緩衝

@CacheConfig

@CacheDefaults

在類層級上提供一些公用配置,比如value值,每個方法都一樣,就只需要在class上配置一次就好了,這個屬性很有用,可惜spring3.1不支援。

  簡單解釋一下,上面的表示官方 文檔中的內容。左邊是spring3.1關於Cache操作的註解;中間是JSR-107規範的註解,spring在4.1版本實現;右邊是解釋。我就懶得翻譯了。

  各個註解的作用與配置方法也有作者寫的不錯,我就直接拿來用了:

 

 
@Cacheable、@CachePut、@CacheEvict 注釋介紹
通過上面的例子,我們可以看到 spring cache 主要使用兩個注釋標籤,即 @Cacheable、@CachePut 和 @CacheEvict,我們總結一下其作用和配置方法。
表 1. @Cacheable 作用和配置方法

@Cacheable  的作用 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩衝
@Cacheable 主要的參數
value 緩衝的名稱,在 spring 設定檔中定義,必須指定至少一個 例如:
@Cacheable(value=”mycache”) 或者
@Cacheable(value={”cache1”,”cache2”}
key 緩衝的 key,可以為空白,如果指定要按照 SpEL 運算式編寫,如果不指定,則預設按照方法的所有參數進行組合 例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition 緩衝的條件,可以為空白,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩衝 例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

表 2. @CachePut 作用和配置方法
@CachePut 的作用 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩衝,和 @Cacheable 不同的是,它每次都會觸發真實方法的調用
@CachePut 主要的參數
value 緩衝的名稱,在 spring 設定檔中定義,必須指定至少一個 例如:
@Cacheable(value=”mycache”) 或者
@Cacheable(value={”cache1”,”cache2”}
key 緩衝的 key,可以為空白,如果指定要按照 SpEL 運算式編寫,如果不指定,則預設按照方法的所有參數進行組合 例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition 緩衝的條件,可以為空白,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩衝 例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

表 3. @CacheEvict 作用和配置方法

 

@CachEvict 的作用 主要針對方法配置,能夠根據一定的條件對緩衝進行清空
@CacheEvict 主要的參數
value 緩衝的名稱,在 spring 設定檔中定義,必須指定至少一個 例如:
@CachEvict(value=”mycache”) 或者
@CachEvict(value={”cache1”,”cache2”}
key 緩衝的 key,可以為空白,如果指定要按照 SpEL 運算式編寫,如果不指定,則預設按照方法的所有參數進行組合 例如:
@CachEvict(value=”testcache”,key=”#userName”)
condition 緩衝的條件,可以為空白,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才清空緩衝 例如:
@CachEvict(value=”testcache”,
condition=”#userName.length()>2”)
allEntries 是否清空所有緩衝內容,預設為 false,如果指定為 true,則方法調用後將立即清空所有緩衝 例如:
@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法執行前就清空,預設為 false,如果指定為 true,則在方法還沒有執行的時候就清空緩衝,預設情況下,如果方法執行拋出異常,則不會清空緩衝 例如:

@CachEvict(value=”testcache”,beforeInvocation=true)

 

  關於上面的註解作用與含義,沒有多少再補充解釋的了,原作者的 文章介紹的也很詳細。這裡補充一下spring-cache的cache配置。cache元素配置除過cache-manager屬性外,還有許多屬性,簡單羅列如下:

 


XML屬性

註解屬性

預設值

含義

cache-manager


cacheManager

預設的cacheManager名稱。一個預設的CacheResolver在cacheManager的後台被初始化,要想更精細的管理緩衝可以考慮設定cache-resolver屬性

cache-resolver


SimpleCacheResolver

CacheResolver的bean名稱,這個非必需屬性,僅僅作為cache-manager屬性的替代

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.