1. 程式人生 > 實用技巧 >博思軟體實訓總結(四)

博思軟體實訓總結(四)

博思軟體實訓總結(四)

Spring快取註解@Cacheable、@CacheEvict、@CachePut使用

從3.1開始,Spring引入了對Cache的支援。其使用方法和原理都類似於Spring對事務管理的支援。Spring Cache是作用在方法上的,其核心思想是這樣的:當我們在呼叫一個快取方法時會把該方法引數和返回結果作為一個鍵值對存放在快取中,等到下次利用同樣的引數來呼叫該方法時將不再執行該方法,而是直接從快取中獲取結果進行返回。所以在使用Spring Cache的時候我們要保證我們快取的方法對於相同的方法引數要有相同的返回結果。

使用Spring Cache需要我們做兩方面的事:

  1. 宣告某些方法使用快取
  2. 配置Spring對Cache的支援

​ 和Spring對事務管理的支援一樣,Spring對Cache的支援也有基於註解和基於XML配置兩種方式。下面我們先來看看基於註解的方式。

1 基於註解的支援

​ Spring為我們提供了幾個註解來支援Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable標記的方法在執行後Spring Cache將快取其返回結果,而使用@CacheEvict標記的方法會在方法執行前或者執行後移除Spring Cache中的某些元素。下面我們將來詳細介紹一下Spring基於註解對Cache的支援所提供的幾個註解。

​ 這裡其實主要學習幾個註解:@CachePut、@Cacheable、@CacheEvict、@CacheConfig。

基礎知識

@Cacheable

@Cacheable 的作用 主要針對方法配置,能夠根據方法的請求引數對其結果進行快取

引數 解釋 example
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”)

@CachePut

@CachePut 的作用 主要針對方法配置,能夠根據方法的返回值對其結果進行快取,和 @Cacheable 不同的是,它每次都會觸發真實方法的呼叫,在其他地方寫的是根據方法的請求引數對其結果進行快取,實際是按方法返回值進行快取的,這裡我就遇到了一個坑,我開始的時候是在Mybatis的Mapper層進行快取的,如下面的程式碼。但是快取到Redis的是Null值,今天看了一博友的部落格,交流了一下,才知道它快取的是方法的返回值,如果把下面update的返回值該為int,在redis中儲存的是int型別,報的錯誤是int無法轉換成User物件。

    @CachePut(value="user",key = "#p0.id")



    @Update({"UPDATE user SET name=#{name},age=#{age} WHERE id =#{id}"})



    void update(User user);
引數 解釋 example
value 快取的名稱,在 spring 配置檔案中定義,必須指定至少一個 @CachePut(value=”my cache”)
key 快取的 key,可以為空,如果指定要按照 SpEL 表示式編寫,如果不指定,則預設按照方法的所有引數進行組合 @CachePut(value=”testcache”,key=”#userName”)
condition 快取的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行快取 @CachePut(value=”testcache”,condition=”#userName.length()>2”)

@CachEvict

@CachEvict 的作用 主要針對方法配置,能夠根據一定的條件對快取進行清空

引數 解釋 example
value 快取的名稱,在 spring 配置檔案中定義,必須指定至少一個 @CacheEvict(value=”my cache”)
key 快取的 key,可以為空,如果指定要按照 SpEL 表示式編寫,如果不指定,則預設按照方法的所有引數進行組合 @CacheEvict(value=”testcache”,key=”#userName”)
condition 快取的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行快取 @CacheEvict(value=”testcache”,condition=”#userName.length()>2”)
allEntries 是否清空所有快取內容,預設為 false,如果指定為 true,則方法呼叫後將立即清空所有快取 @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法執行前就清空,預設為 false,如果指定為 true,則在方法還沒有執行的時候就清空快取,預設情況下,如果方法執行丟擲異常,則不會清空快取 @CachEvict(value=”testcache”,beforeInvocation=true)

@CacheConfig

所有的@Cacheable()裡面都有一個value=“xxx”的屬性,這顯然如果方法多了,寫起來也是挺累的,如果可以一次性宣告完 那就省事了,有了@CacheConfig這個配置,@CacheConfig is a class-level annotation that allows to share the cache names,如果你在你的方法寫別的名字,那麼依然以方法的名字為準。

1.1 @Cacheable

​ @Cacheable可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支援快取的,當標記在一個類上時則表示該類所有的方法都是支援快取的。對於一個支援快取的方法,Spring會在其被呼叫後將其返回值快取起來,以保證下次利用同樣的引數來執行該方法時可以直接從快取中獲取結果,而不需要再次執行該方法。Spring在快取方法的返回值時是以鍵值對進行快取的,值就是方法的返回結果,至於鍵的話,Spring又支援兩種策略,預設策略和自定義策略,這個稍後會進行說明。需要注意的是當一個支援快取的方法在物件內部被呼叫時是不會觸發快取功能的。@Cacheable可以指定三個屬性,value、key和condition。

1.1.1 value屬性指定Cache名稱

​ value屬性是必須指定的,其表示當前方法的返回值是會被快取在哪個Cache上的,對應Cache的名稱。其可以是一個Cache也可以是多個Cache,當需要指定多個Cache時其是一個數組。

@Cacheable("cache1")//Cache是發生在cache1上的

public User find(Integer id) {

returnnull;

}

@Cacheable({"cache1", "cache2"})//Cache是發生在cache1和cache2上的

public User find(Integer id) {

returnnull;

}

1.1.2 使用key屬性自定義key

​ key屬性是用來指定Spring快取方法的返回結果時對應的key的。該屬性支援SpringEL表示式。當我們沒有指定該屬性時,Spring將使用預設策略生成key。我們這裡先來看看自定義策略,至於預設策略會在後文單獨介紹。

​ 自定義策略是指我們可以通過Spring的EL表示式來指定我們的key。這裡的EL表示式可以使用方法引數及它們對應的屬性。使用方法引數時我們可以直接使用“#引數名”或者“#p引數index”。下面是幾個使用引數作為key的示例。

例子看下面

1.1.3 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;

}

1.2 @CachePut

​ 在支援Spring Cache的環境下,對於使用@Cacheable標註的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的快取元素,如果存在就不再執行該方法,而是直接從快取中獲取結果進行返回,否則才會執行並將返回結果存入指定的快取中。@CachePut也可以宣告一個方法支援快取功能。與@Cacheable不同的是使用@CachePut標註的方法在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的快取中。

@CachePut也可以標註在類上和方法上。使用@CachePut時我們可以指定的屬性跟@Cacheable是一樣的。

@CachePut("users")//每次都會執行方法,並將結果存入指定的快取中

public User find(Integer id) {

returnnull;

}

1.3 @CacheEvict

​ @CacheEvict是用來標註在需要清除快取元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發快取的清除操作。@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的語義與@Cacheable對應的屬性類似。即value表示清除操作是發生在哪些Cache上的(對應Cache的名稱);key表示需要清除的是哪個key,如未指定則會使用預設策略生成的key;condition表示清除操作發生的條件。下面我們來介紹一下新出現的兩個屬性allEntries和beforeInvocation。

1.3.1 allEntries屬性

​ 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);

}

1.3.2 beforeInvocation屬性

​ 清除操作預設是在對應方法成功執行之後觸發的,即方法如果因為丟擲異常而未能成功返回時也不會觸發清除操作。使用beforeInvocation可以改變觸發清除操作的時間,當我們指定該屬性值為true時,Spring會在呼叫該方法之前清除快取中的指定元素。

@CacheEvict(value="users", beforeInvocation=true)

public void delete(Integer id) {

System.out.println("delete user by id: " + id);

}

​ 其實除了使用@CacheEvict清除快取元素外,當我們使用Ehcache作為實現時,我們也可以配置Ehcache自身的驅除策略,其是通過Ehcache的配置檔案來指定的。由於Ehcache不是本文描述的重點,這裡就不多贅述了,想了解更多關於Ehcache的資訊,請檢視我關於Ehcache的專欄。

1.4 @Caching

​ @Caching註解可以讓我們在一個方法或者類上同時指定多個Spring Cache相關的註解。其擁有三個屬性:cacheable、put和evict,分別用於指定@Cacheable、@CachePut和@CacheEvict。

@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),

​ @CacheEvict(value = "cache3", allEntries = true) })

public User find(Integer id) {

returnnull;

}

1.5 使用自定義註解

​ Spring允許我們在配置可快取的方法時使用自定義的註解,前提是自定義的註解上必須使用對應的註解進行標註。如我們有如下這麼一個使用@Cacheable進行標註的自定義註解。

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Cacheable(value="users")

public @interface MyCacheable {

}

​ 那麼在我們需要快取的方法上使用@MyCacheable進行標註也可以達到同樣的效果。

@MyCacheable

public User findById(Integer id) {

System.out.println("find user by id: " + id);

User user = new User();

user.setId(id);

user.setName("Name" + id);

return user;

}

2 配置Spring對Cache的支援

2.1 宣告對Cache的支援

2.1.1 基於註解

​ 配置Spring對基於註解的Cache的支援,首先我們需要在Spring的配置檔案中引入cache名稱空間,其次通過<cache:annotation-driven />就可以啟用Spring對基於註解的Cache的支援

<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"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/cache

http://www.springframework.org/schema/cache/spring-cache.xsd">

cache:annotation-driven/

cache:annotation-driven/有一個cache-manager屬性用來指定當前所使用的CacheManager對應的bean的名稱,預設是cacheManager,所以當我們的CacheManager的id為cacheManager時我們可以不指定該引數,否則就需要我們指定了。

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等快取註解。

2.1.2 基於XML配置

​ 除了使用註解來宣告對Cache的支援外,Spring還支援使用XML來宣告對Cache的支援。這主要是通過類似於aop:advice的cache:advice來進行的。在cache名稱空間下定義了一個cache:advice元素用來定義一個對於Cache的advice。其需要指定一個cache-manager屬性,預設為cacheManager。cache:advice下面可以指定多個cache:caching元素,其有點類似於使用註解時的@Caching註解。cache:caching元素下又可以指定cache:cacheable、cache:cache-put和cache:cache-evict元素,它們類似於使用註解時的@Cacheable、@CachePut和@CacheEvict。下面來看一個示例:

<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>

​ 上面配置定義了一個名為cacheAdvice的cache:advice,其中指定了將快取findById方法和find方法到名為users的快取中。這裡的方法還可以使用萬用字元“”,比如“find”表示任何以“find”開始的方法。

​ 有了cache:advice之後,我們還需要引入aop名稱空間,然後通過aop:config指定定義好的cacheAdvice要應用在哪些pointcut上。如:

<aop:config proxy-target-class="false">

<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.xxx.UserService.*(..))"/>

</aop:config>

​ 上面的配置表示在呼叫com.xxx.UserService中任意公共方法時將使用cacheAdvice對應的cache:advice來進行Spring Cache處理。更多關於Spring Aop的內容不在本文討論範疇內。

2.2 配置CacheManager

​ CacheManager是Spring定義的一個用來管理Cache的介面。Spring自身已經為我們提供了兩種CacheManager的實現,一種是基於Java API的ConcurrentMap,另一種是基於第三方Cache實現——Ehcache,如果我們需要使用其它型別的快取時,我們可以自己來實現Spring的CacheManager介面或AbstractCacheManager抽象類。下面分別來看看Spring已經為我們實現好了的兩種CacheManager的配置示例。

2.2.1 基於ConcurrentMap的配置

<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">

<property name="caches">

​ <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="xxx"/>

​ 上面的配置使用的是一個SimpleCacheManager,其中包含一個名為“xxx”的ConcurrentMapCache。

2.2.2 基於Ehcache的配置

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

<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache-spring.xml"/>

​ 上面的配置使用了一個Spring提供的EhCacheCacheManager來生成一個Spring的CacheManager,其接收一個Ehcache的CacheManager,因為真正用來存入快取資料的還是Ehcache。Ehcache的CacheManager是通過Spring提供的EhCacheManagerFactoryBean來生成的,其可以通過指定ehcache的配置檔案位置來生成一個Ehcache的CacheManager。若未指定則將按照Ehcache的預設規則取classpath根路徑下的ehcache.xml檔案,若該檔案也不存在,則獲取Ehcache對應jar包中的ehcache-failsafe.xml檔案作為配置檔案。更多關於Ehcache的內容這裡就不多說了,它不屬於本文討論的內容,欲瞭解更多關於Ehcache的內容可以參考我之前釋出的Ehcache系列文章,也可以參考官方文件等。

3 鍵的生成策略

​ 鍵的生成策略有兩種,一種是預設策略,一種是自定義策略。

3.1 預設策略

​ 預設的key生成策略是通過KeyGenerator生成的,其預設策略如下:

  1. 如果方法沒有引數,則使用0作為key。
  2. 如果只有一個引數的話則使用該引數作為key。
  3. 如果引數多餘一個的話則使用所有引數的hashCode作為key。

​ 如果我們需要指定自己的預設策略的話,那麼我們可以實現自己的KeyGenerator,然後指定我們的Spring Cache使用的KeyGenerator為我們自己定義的KeyGenerator。

1.使用基於註解的配置時是通過cache:annotation-driven指定的.

<cache:annotation-driven key-generator="userKeyGenerator"/>

<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>

2.而使用基於XML配置時是通過cache:advice來指定的。

<cache:advice id="cacheAdvice" cache-manager="cacheManager" key-generator="userKeyGenerator">

</cache:advice>

​ 需要注意的是此時我們所有的Cache使用的Key的預設生成策略都是同一個KeyGenerator。

3.2 自定義策略

​ 自定義策略是指我們可以通過Spring的EL表示式來指定我們的key。這裡的EL表示式可以使用方法引數及它們對應的屬性。使用方法引數時我們可以直接使用“#引數名”或者“#p引數index”。下面是幾個使用引數作為key的示例。

@Cacheable(value="users", key="#id")

public User find(Integer id) {

returnnull;

}

@Cacheable(value="users", key="#p0")

public User find(Integer id) {

returnnull;

}

@Cacheable(value="users", key="#user.id")

public User find(User user) {

returnnull;

}

@Cacheable(value="users", key="#p0.id")

public User find(User user) {

returnnull;

}

​ 除了上述使用方法引數作為key之外,Spring還為我們提供了一個root物件可以用來生成key。通過該root物件我們可以獲取到以下資訊。

屬性名稱 描述 示例
methodName 當前方法名 #root.methodName
method 當前方法 #root.method.name
target 當前被呼叫的物件 #root.target
targetClass 當前被呼叫的物件的class #root.targetClass
args 當前方法引數組成的陣列 #root.args[0]
caches 當前被呼叫的方法使用的Cache #root.caches[0].name

​ 當我們要使用root物件的屬性作為key時我們也可以將“#root”省略,因為Spring預設使用的就是root物件的屬性。如:

@Cacheable(value={"users", "xxx"}, key="caches[1].name")

public User find(User user) {

returnnull;

}

4 Spring單獨使用Ehcache

​ 前面介紹的內容是Spring內建的對Cache的支援,其實我們也可以通過Spring自己單獨的使用Ehcache的CacheManager或Ehcache物件。通過在Application Context中配置EhCacheManagerFactoryBean和EhCacheFactoryBean,我們就可以把對應的EhCache的CacheManager和Ehcache物件注入到其它的Spring bean物件中進行使用。

4.1 EhCacheManagerFactoryBean

EhCacheManagerFactoryBean是Spring內建的一個可以產生Ehcache的CacheManager物件的FactoryBean。其可以通過屬性configLocation指定用於建立CacheManager的Ehcache配置檔案的路徑,通常是ehcache.xml檔案的路徑。如果沒有指定configLocation,則將使用預設位置的配置檔案建立CacheManager,這是屬於Ehcache自身的邏輯,即如果在classpath根路徑下存在ehcache.xml檔案,則直接使用該檔案作為Ehcache的配置檔案,否則將使用ehcache-xxx.jar中的ehcache-failsafe.xml檔案作為配置檔案來建立Ehcache的CacheManager。此外,如果不希望建立的CacheManager使用預設的名稱(在ehcache.xml檔案中定義的,或者是由CacheManager內部定義的),則可以通過cacheManagerName屬性進行指定。下面是一個配置EhCacheManagerFactoryBean的示例。

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

<property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>

<property name="cacheManagerName" value="cacheManagerName"/>

4.2 EhCacheFactoryBean

​ EhCacheFactoryBean是用來產生Ehcache的Ehcache物件的FactoryBean。定義EhcacheFactoryBean時有兩個很重要的屬性我們可以來指定。一個是cacheManager屬性,其可以指定將用來獲取或建立Ehcache的CacheManager物件,若未指定則將通過CacheManager.create()獲取或建立預設的CacheManager。另一個重要屬性是cacheName,其表示當前EhCacheFactoryBean對應的是CacheManager中的哪一個Ehcache物件,若未指定預設使用beanName作為cacheName。若CacheManager中不存在對應cacheName的Ehcache物件,則將使用CacheManager建立一個名為cacheName的Cache物件。此外我們還可以通過EhCacheFactoryBean的timeToIdle、timeToLive等屬性指定要建立的Cache的對應屬性,注意這些屬性只對CacheManager中不存在對應Cache時新建的Cache才起作用,對已經存在的Cache將不起作用,更多屬性設定請參考Spring的API文件。此外還有幾個屬性是對不管是已經存在還是新建立的Cache都起作用的屬性:statisticsEnabled、sampledStatisticsEnabled、disabled、blocking和cacheEventListeners,其中前四個預設都是false,最後一個表示為當前Cache指定CacheEventListener。下面是一個定義EhCacheFactoryBean的示例。

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

<property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>

<property name="cacheManagerName" value="cacheManagerName"/>

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

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

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

Spring-Cache key設定注意事項

為了提升專案的併發效能,考慮引入本地記憶體Cache,對:外部資料來源訪問、Restful API呼叫、可重用的複雜計算 等3種類型的函式處理結果進行快取。目前採用的是Spring Cache的@Cacheable註解方式,快取具體實現選取的是Guava Cache。

具體快取的配置此處不再介紹,重點對於key的配置進行說明:

1、基本形式

@Cacheable(value="cacheName", key"#id")
public ResultDTO method(int id);

2、組合形式

@Cacheable(value="cacheName", key"T(String).valueOf(#name).concat('-').concat(#password))
public ResultDTO method(int name, String password);

3、物件形式

@Cacheable(value="cacheName", key"#user.id)
public ResultDTO method(User user);

4、自定義Key生成器

@Cacheable(value="gomeo2oCache", keyGenerator = "keyGenerator")
public ResultDTO method(User user);

例子

有一個尤其需要注意的坑:Spring預設的SimpleKeyGenerator是不會將函式名組合進key中的

舉個栗子:

@Component



    public class CacheTestImpl implements CacheTest {



        @Cacheable("databaseCache")



        public Long test1()



        { return 1L; }



 



        @Cacheable("databaseCache")



        public Long test2()



        { return 2L; }



 



        @Cacheable("databaseCache")



        public Long test3()



        { return 3L; }



 



        @Cacheable("databaseCache")



        public String test4()



        { return "4"; }



 



    }

我們期望的輸出是:

1
2
3
4
而實際上的輸出是:

1
1
1
ClassCastException: java.lang.Long cannot be cast to java.lang.String

此外,原子型別的陣列,直接作為key使用也是不會生效的

為了解決上述2個問題,自定義了一個KeyGenerator如下:

class CacheKeyGenerator implements KeyGenerator {



 



    // custom cache key



    public static final int NO_PARAM_KEY = 0;



    public static final int NULL_PARAM_KEY = 53;



    



    @Override



    public Object generate(Object target, Method method, Object... params) {



 



        StringBuilder key = new StringBuilder();



        key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append(":");



        if (params.length == 0) {



            return key.append(NO_PARAM_KEY).toString();



        }



        for (Object param : params) {



            if (param == null) {



                log.warn("input null param for Spring cache, use default key={}", NULL_PARAM_KEY);



                key.append(NULL_PARAM_KEY);



            } else if (ClassUtils.isPrimitiveArray(param.getClass())) {



                int length = Array.getLength(param);



                for (int i = 0; i < length; i++) {



                    key.append(Array.get(param, i));



                    key.append(',');



                }



            } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {



                key.append(param);



            } else {



                log.warn("Using an object as a cache key may lead to unexpected results. " +



                        "Either use @Cacheable(key=..) or implement CacheKey. Method is " + target.getClass() + "#" + method.getName());



                key.append(param.hashCode());



            }



            key.append('-');



        }



 



        String finalKey = key.toString();



        long cacheKeyHash = Hashing.murmur3_128().hashString(finalKey, Charset.defaultCharset()).asLong();



        log.debug("using cache key={} hashCode={}", finalKey, cacheKeyHash);



        return key.toString();



    }



}