1. 程式人生 > >java中利用spring cache解耦業務中的快取

java中利用spring cache解耦業務中的快取

雖然以前實現快取的方式,是定義了快取操作介面,可以靈活實現不同的快取,可畢竟精力有限,要完成不同的快取實現也是件麻煩的事。更要命的是,業務程式碼中有大量快取操作的程式碼,耦合度太高,看著很不優雅。
所以呢,抽空了解了一下其它實現方案。這不,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屬性的替代

key-generator


SimpleKeyGenerator

自定義的 key generator

error-handler


SimpleCacheErrorHandler

自定義的Cache Error handler,預設情況下,異常會直接丟擲給客戶端

mode

mode

proxy

spring cache預設使用了spring的AOP框架來通過proxy的方式處理註解,另一種可代替的方式就是aspectj。通過Spring AOP方式的cache註解,無法在內部呼叫的時候被proxy,因此也就在內部呼叫的時候快取註解會失效,而aspectj的AOP則可以解決這個問題。

proxy-target-class

proxyTargetClass

false

僅適用於proxy模式,控制類上的註解@Cacheable或@CacheEvict採用哪種快取代理。如果proxy-target-class屬性設定為true,那麼將建立基於類的代理。 如果proxy-target-class為false,那麼將建立基於標準JDK介面的代理。具體可以參考AOP的proxy-target-class屬性

order

order

Ordered.LOWEST_PRECEDENCE

確定bean註解中@Cacheable或@CacheEvict中cache advice的順序,沒有指定則意味著使用AOP決定的advice順序。具體可以參考A

以上是雲棲社群小編為您精心準備的的內容,在雲棲社群的部落格、問答、公眾號、人物、課程等欄目也有的相關內容,歡迎繼續使用右上角搜尋按鈕進行搜尋快取 , 引數 , 程式碼 , cache , 配置 屬性 spring mvc cache快取、spring cache快取過期、spring cache快取字典、spring快取 設定cache、spring cache 快取,以便於您獲取更多的相關知識。

相關推薦

java利用spring cache業務快取

雖然以前實現快取的方式,是定義了快取操作介面,可以靈活實現不同的快取,可畢竟精力有限,要完成不同的快取實現也是件麻煩的事。更要命的是,業務程式碼中有大量快取操作的程式碼,耦合度太高,看著很不優雅。 所以呢,抽空了解了一下其它實現方案。這不,spring3.1開始,支援基於註解的快取,算是目前我比較可以接

web 專案中報錯解決,java.io.FileNotFoundException: druid.properties (系統找不到指定的檔案);【spring工廠開發】

使用 Tomcat9.0 ,   spring5.0框架原始工廠類解耦,druid-1.0.9jar版本,JDK9,MSQL8版本資料庫  模擬web頁面登入案例時候出現druid.properties

spring原始碼汲取營養:模仿spring事件釋出機制,業務程式碼

前言 最近在專案中做了一項優化,對業務程式碼進行解耦。我們部門做的是警用系統,通俗的說,可理解為110報警。一條警情,會先後經過接警員、處警排程員、一線警員,警情是需要記錄每一步的日誌,是要可追溯的,比如報警人張小三在2019-12-02 00:02:01時間報警,接警員A在1分鐘後,將該警情記錄完成,並分派

[Java][web]利用Spring隨時隨地獲得Request和Session

login html article -a private 綁定 ren alt 結束 利用Spring隨時隨地獲得Request和Session 一、準備工作: 在web.xml中加入 <listener> <

unity利用事件機制程式碼(四)

在某一個類呼叫另一個類裡的方法的時候,往往需要這個類的例項,這在繁複的專案中,往往沒有那麼方便,需要在這個類中不斷的通過物件圖語言導航到我們需要的地方。但如果使用事件機制的,在這個類裡發起一個事件,在另一個類了處理這個事件,就可以不需要這個例項物件,就能完成。 這裡有兩杯水

Spring:WebServlet 不能注入Bean物件

1. 前言 最近在研究Spring IOC、AOP以及和Mybatis整合的時候發現在Spring中使用Servlet+Service+Dao(Mybatis)的時候,發現在Controller層也就是Servlet中不能通過@Autowired注入Bean物件。這個時候我就納悶了,在S

Java 反射+工廠模式實現

在實際開發中,工廠模式是經常被用到的。 工廠模式的好處: 工廠模式可以達到類的解耦。 工廠類中工廠方法過多也是個問題,每新增一個類,則新增一個工廠方法,這會導致工廠方法過多。恰好,反射可以建立類的例項物件,而且可以採用統一操作Class.forNa

Matlab利用null函式齊次線性方程組

摘自:http://blog.csdn.net/masibuaa/article/details/8119032 有齊次線性方程AX=0,且rank(A)=r<n時,該方程有無窮多個解,可以用matlab 中的命令 x=null(A, r)求其基礎解系.其中:r=ra

在idea利用Spring進行面向切面程式設計(AOP)的一個例子

(1)在idea中新建立一個maven專案aopAspectj,,編寫POM檔案,匯入jar包: <dependencies> <!--aspectj依賴--> <dependency> <grou

spring事務、當業務並沒有異常丟擲時執行一半的事務無法正常出發導致資料一致性遭到破壞、事務不起作用

spring 事務:當所攔截的方法有指定錯誤丟擲的時候觸發事務的執行。 有些情況下正常開發的業務並沒有異常的發生、但是業務要求並沒有滿足的時候並不會有異常的產生。這個時候需要我們自己手動丟擲異常。觸發事務、保證資料的一致性。 手動丟擲異常有兩種方式: 一:手動丟擲異常:

[Java面試題]Spring總結以及在面試的一些問題.

    2)Bean的完整生命週期 (十一步驟)【瞭解內容,但是對於spring內部操作理解有一定幫助】①instantiate bean物件例項化②populate properties 封裝屬性③如果Bean實現BeanNameAware 執行 setBeanName④如果Bean實現BeanFactor

AndroidJNI使用詳(1)---EclipseNDK配置So檔案生成

1、NDK下載和配置 NDK下載地址:http://www.androiddevtools.cn/ NDK下載完成後,選擇Eclipse上方Window選單Preferences - Android - NDK 在NDK&nb

利用spring的AOP來實現Redis快取

為什麼使用Redis 資料查詢時每次都需要從資料庫查詢資料,資料庫壓力很大,查詢速度慢,因此設定快取層,查詢資料時先從redis中查詢,如果查詢不到,則到資料庫中查詢,然後將資料庫中查詢的資料放到redis中一份,下次查詢時就能直接從redis中查到,不需要查

SPRING CACHE REDIS 註解式實現快取策略

為了解決資料庫查詢效率瓶頸,提升併發系統能力,快取的應用已經非常普遍和必要了。剛接觸REDIS時,如何使SPRING框架與REDIS更高效地整合,困擾了我很長時間。 先說下不使用SPRING CACHE時的兩種快取應用模式: 1.使用redis作為持久層的二級快

Python利用filter()函式刪除1-100的素數

法一: import math def fil(n): flag=0 #設定flag for i in range(2,int(math.sqrt(n)+1)): if n%i==0: #判斷是否為素數

spring的Ioc技術是怎樣實現的 原文地址 : http://blog.csdn.net/liang5603/article/details/52002994

ioc容器 可能 深入 修改 知識 動態 出現 工廠方法 邏輯 1. IoC理論的背景我們都知道,在采用面向對象方法設計的軟件系統中,它的底層實現都是由N個對象組成的,所有的對象通過彼此的合作,最終實現系統的業務邏輯。圖1:軟件系統中耦合的對象如果我們打開機械式手表的後蓋,

mp-redux:小程式業務與檢視,讓測試更容易

專案地址:點我,歡迎star和issue mp-redux 一個用於小程式和輕量級H5應用的狀態管理工具, 使用方法是一個簡化版本的Redux。之所以是適用於輕量級應用,主要是因為沒有實現元件間的資料共享。因此不適合於複雜,龐大的前端應用。 是否你需要使用它? 如果你也和我有同樣的困惑,那麼你就該嘗試

利用Spring IOC DI 實現軟體分層

1.軟體分層思想 在軟體領域有MVC軟體設計思想,指導著軟體開發過程。在javaee開發領域,javaee的經典三層架構MVC設計思想的經典應用。而在軟體設計思想中,追求的是"高內聚 低耦合"的目標,利用Spring的IOC 和 DI 可以非常方便的實現這個需求。 2

java重試工具庫: 實現業務邏輯與重試邏輯的

對於開發過網路應用程式的程式設計師來說,重試並不陌生,由於網路的擁堵和波動,此刻不能訪問服務的請求,也許過一小段時間就可以正常訪問了。比如下面這段給某個手機號發SMS的虛擬碼:// 傳送SMSpublic boolean sendSMS(String phone, String content){ int r

趣談spring事件:業務與非同步呼叫

分析需求引入事件機制使用spring的事件機制有助於對我們的專案進一步的解耦。假如現在我們面臨一個需求: 我需要在使用者註冊成功的時候,根據使用者提交的郵箱、手機號資訊,向用戶傳送郵箱認證和手機號簡訊通知。傳統的做法之一是在我們的UserService層注入郵件