一.spring cache
Spring Cache是作用在方法上的,其核心思想是這樣的:當我們在調用一個緩存方法時會把該方法參數和返回結果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時將不再執行該方法,
而是直接從緩存中獲取結果進行返回。所以在使用Spring Cache的時候我們要保證我們緩存的方法對於相同的方法參數要有相同的返回結果。使用Spring Cache需要我們做兩方面的事:
1.聲明某些方法使用緩存
2.配置Spring對Cache的支持
Spring為我們提供了幾個注解來支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable標記的方法在執行后Spring Cache將緩存其返回結果,而使用@CacheEvict標記的方法
會在方法執行前或者執行后移除Spring Cache中的某些元素。這里只介紹幾個常用的屬性。
基於注解的相關概念:
1.@Cacheable
可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支持緩存的,當標記在一個類上時則表示該類所有的方法都是支持緩存的。對於一個支持緩存的方法,
Spring會在其被調用后將其返回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中獲取結果,而不需要再次執行該方法。Spring在緩存方法的返回值時是以鍵值對進行緩存的,
值就是方法的返回結果,至於鍵的話,Spring又支持兩種策略,默認策略和自定義策略。
需要注意的是當一個支持緩存的方法在對象內部被調用時是不會觸發緩存功能的。@Cacheable可以指定三個屬性,value、key和condition。
2.@CachePut
在支持Spring Cache的環境下,對於使用@Cacheable標注的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的緩存元素,如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,
否則才會執行並將返回結果存入指定的緩存中。而@CachePut也可以聲明一個方法支持緩存功能。與@Cacheable不同的是使用@CachePut標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,
而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。
@CachePut也可以標注在類上和方法上。使用@CachePut時我們可以指定的屬性跟@Cacheable是一樣的。
3.@CacheEvict
@CacheEvict是用來標注在需要清除緩存元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發緩存的清除操作。@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。
其中value、key和condition的語義與@Cacheable對應的屬性類似。即value表示清除操作是發生在哪些Cache上的(對應Cache的名稱);key表示需要清除的是哪個key,如未指定則會使用默認策略生成的key;condition表示清除操作發生的條件。
屬性一 :value
必須指定的,其表示當前方法的返回值是會被緩存在哪個Cache上的,對應Cache的名稱,為ehcache.xml中的<cache name="myCache"/> 。其可以是一個Cache也可以是多個Cache,當需要指定多個Cache時其是一個數組。
屬性二 :key
緩存的Key,當我們沒有指定該屬性時,Spring將使用默認策略生成key(表示使用方法的參數類型及參數值作為key),key屬性是用來指定Spring緩存方法的返回結果時對應的key的。該屬性支持SpringEL表達式。
我們還可以自定義策略:自定義策略是指我們可以通過Spring的EL表達式來指定我們的key。這里的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p參數index”
key的生成策略有兩種:一種是默認策略,一種是自定義策略
¹默認的key生成策略是通過KeyGenerator生成的,其默認策略如下:
1.如果方法沒有參數,則使用0作為key。
2.如果只有一個參數的話則使用該參數作為key。
3.如果參數多余一個的話則使用所有參數的hashCode作為key
²自定義策略是指我們可以通過Spring的EL表達式來指定我們的key。這里的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p參數index
屬性三 :condition
有的時候我們可能並不希望緩存一個方法所有的返回結果。通過condition屬性可以實現這一功能。
condition屬性默認為空,表示將緩存所有的調用情形。其值是通過SpringEL表達式來指定的,當為true時表示進行緩存處理;當為false時表示不進行緩存處理,即每次調用該方法時該方法都會執行一次。
如下示例表示只有當user的id為偶數時才會進行緩存
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}
屬性四 :allEntries
是boolean類型,表示是否需要清除緩存中的所有元素。默認為false,表示不需要。當指定了allEntries為true時,Spring Cache將忽略指定的key。有的時候我們需要Cache一下清除所有的元素,這比一個一個清除元素更有效率。
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
屬性五 :beforeInvocation
清除操作默認是在對應方法成功執行之后觸發的,即方法如果因為拋出異常而未能成功返回時也不會觸發清除操作。使用beforeInvocation可以改變觸發清除操作的時間,當我們指定該屬性值為true時,Spring會在調用該方法之前清除緩存中的指定元素。
@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
注意我們也可以使用ehcache的去除策略最近使用(LRU)"策略,其它還有先入先出FIFO,最少使用LFU,較少使用LRU
基於注解配置:
配置Spring對基於注解的Cache的支持,首先我們需要在Spring的配置文件中引入cache命名空間,其次通過<cache:annotation-driven />就可以啟用Spring對基於注解的Cache的支持。
<cache:annotation-driven/>有一個mode屬性,可選值有proxy和aspectj。默認是使用proxy。當mode為proxy時,只有緩存方法在外部被調用的時候Spring Cache才會發生作用,
這也就意味着如果一個緩存方法在其聲明對象內部被調用時Spring Cache是不會發生作用的。而mode為aspectj時就不會有這種問題。另外使用proxy時,只有public方法上的@Cacheable等標注才會起作用,
如果需要非public方法上的方法也可以使用Spring Cache時把mode設置為aspectj。此外,<cache:annotation-driven/>還可以指定一個proxy-target-class屬性,表示是否要代理class,默認為false。
我們前面提到的@Cacheable、@cacheEvict等也可以標注在接口上,這對於基於接口的代理來說是沒有什么問題的,但是需要注意的是當我們設置proxy-target-class為true或者mode為aspectj時,是直接基於class進行操作的,
定義在接口上的@Cacheable等Cache注解不會被識別到,那對應的Spring Cache也不會起作用了。
需要注意的是<cache:annotation-driven/>只會去尋找定義在同一個ApplicationContext下的@Cacheable等緩存注解。
基於XML配置:
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="users">
<cache:cacheable method="findById" key="#p0"/>
<cache:cacheable method="find" key="#user.id"/>
<cache:cache-evict method="deleteAll" all-entries="true"/>
</cache:caching>
</cache:advice>
<aop:config proxy-target-class="false">
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.xxx.UserService.*(..))"/>
</aop:config>
二.EhCache 是一個純Java的進程內緩存框架,具有快速、精干等特點。
ehcache官網:http://www.ehcache.org/ 可以下載文檔看看,里面寫的很清楚。
<diskStore> : 當內存緩存中對象數量超過maxElementsInMemory時,將緩存對象寫到磁盤緩存中(需對象實現序列化接口)
<diskStore path=""> : 用來配置磁盤緩存使用的物理路徑,Ehcache磁盤緩存使用的文件后綴名是*.data和*.index
name : "緩存名稱,cache的唯一標識(ehcache會把這個cache放到HashMap里)
maxElementsInMemory : 緩存最大個數。
eternal="false" : 對象是否永久有效,一但設置了,timeout將不起作用。 (必須設置)
maxEntriesLocalHeap="1000" : 堆內存中最大緩存對象數,0沒有限制(必須設置)
maxEntriesLocalDisk= "1000" : 硬盤最大緩存個數。
overflowToDisk="false" : 當緩存達到maxElementsInMemory值是,是否允許溢出到磁盤(必須設置)(內存不足時,是否啟用磁盤緩存。)
diskSpoolBufferSizeMB : 這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區。
diskPersistent="false" : 磁盤緩存在JVM重新啟動時是否保持(默認為false)
timeToIdleSeconds="0" : 導致元素過期的訪問間隔(秒為單位),即當緩存閑置n秒后銷毀。 當eternal為false時,這個屬性才有效,0表示可以永遠空閑,默認為0
timeToLiveSeconds="600" : 元素在緩存里存在的時間(秒為單位),即當緩存存活n秒后銷毀. 0 表示永遠存在不過期
memoryStoreEvictionPolicy="LFU" : 當達到maxElementsInMemory時,如何強制進行驅逐默認使用"最近使用(LRU)"策略,其它還有先入先出FIFO,最少使用LFU,較少使用LRU
diskExpiryThreadIntervalSeconds :磁盤失效線程運行時間間隔,默認是120秒。
clearOnFlush : 內存數量最大時是否清除。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">
<diskStore path="java.io.tmpdir"/>
<defaultCache maxEntriesLocalHeap="10000" eternal="false" overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="myCache" maxEntriesLocalHeap="10000" maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="30" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>