聊聊Spring的緩存及如何自定義緩存


Spring的緩存機制

Spring的緩存不是一種具體的緩存實現方案,它底層需要依賴EhCache、Guava等具體的緩存工具。應用程序只要面向Spring緩存API編程,應用底層的緩存實現可以在不同的緩存之間自由切換,應用程序無須任何改變,只需要對配置略作修改即可。

啟用Spring緩存

(1) 創建一個有@Configuration注解的配置類,添加@EnableCaching注解開啟緩存機制,並繼承CachingConfigurerSupport自定義緩存(或xml配置:<cache:annotation-driven cache-mangager="緩存管理器ID"/>) 

(2) 針對不同的緩存實現配置對應的緩存管理器。

① spring-context-*.jar 包含了 cache 需要的類。

② 如果引入了spring-data-redis,要注意版本的問題。我使用spring 5.0.2 與 spring-data-redis 2.0.2.RELEASE是兼容的,但是跟spring-data-redis 2.1.9.RELEASE是有問題的(因為該版本使用的spring與項目的spring版本有沖突)。

③ 只有使用public定義的方法才可以被緩存,而private方法、protected 方法或者使用default 修飾符的方法都不能被緩存。 當在一個類上使用注解時,該類中每個公共方法的返回值都將被緩存到指定的緩存項中或者從中移除。

④ 命名空間:

xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"

Spring內置緩存實現的配置

Spring內置的緩存實現只是一種內存中的緩存,並非真正的緩存實現,因此通常只能用於簡單的測試環境,不建議在實際項目中使用Spring內置的緩存實現。

Spring內置的緩存實現使用SimpleCacheManager作為緩存管理器,使用SimpleCacheManager配置緩存非常簡單,直接在Spring容器中配置該Bean,然后通過<property.../>驅動該緩存管理器執行setCaches()放來來設置緩存區即可。

SimpleCacheManager是一種內存中的緩存區,底層直接使用了JDK的ConcurrentMap來實現緩存,SimpleCacheManager使用了ConcurrentMapCacheFactoryBean作為緩存區,每個ConcurrentMapCacheFactoryBean配置一個緩存區。

xml配置:

<cache:annotation-driven cache-mangager="cacheManager"/>

<!-- 使用SimpleCacheManager配置Spring內置的緩存管理器 -->
<bean id="cacheManager" class="org.springframework.cache.suppot.SimpleCacheManager">
    <!-- 配置緩存區 -->
    <property name="caches">
        <set>
            <!-- 使用ConcurrentMapCacheFactoryBean配置緩存區
                下面列出多個緩存區,p:name用於為緩存區指定名字 -->
            <bean class="org.springframeword.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
            <bean class="org.springframeword.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="users"/>
        </set>
    </property>
</bean>
上面配置文件使用SimpleCacheManager配置了Spring內置的緩存管理器,並為該緩存管理器配置有兩個緩存區:default和users——這些緩存區的名字很重要,后面使用注解驅動緩存時需要根據緩存區的名字來講緩存數據放入指定緩存區中。

在實際應用中,開發者可以根據自己的需要,配置更多的緩存區,一般來說,應用有多少個組件需要緩存,程序就應該配置多少個緩存區。

Java配置:

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.List;

@EnableCaching
@Configuration
public class SpringCacheConfig2  extends CachingConfigurerSupport {

    @Resource(name = "simpleCacheManager")
    private CacheManager simpleCacheManager;

    @Override
    public CacheManager cacheManager() {
        return simpleCacheManager;
    }

    @Bean
    public SimpleCacheManager simpleCacheManager(List<Cache> cacheList){
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(cacheList);
        return cacheManager;
    }

    @Bean
    public ConcurrentMapCacheFactoryBean defaultCacheBean(){
        ConcurrentMapCacheFactoryBean defaultFactoryBean = new ConcurrentMapCacheFactoryBean();
        defaultFactoryBean.setName("default");
        return defaultFactoryBean;
    }

    @Bean
    public ConcurrentMapCacheFactoryBean userCacheBean(){
        ConcurrentMapCacheFactoryBean usersFactoryBean = new ConcurrentMapCacheFactoryBean();
        usersFactoryBean.setName("users");
        return usersFactoryBean;
    }
}

CachingConfigurerSupport類

cacheManager方法

指定使用哪一種緩存。Spring內置了SimpleCacheManager。

keyGenerator方法

指定默認的key生成方式。Spring內置了SimpleKeyGenerator。 

/**
 * 自定義Redis Key生產策略
 * 在使用是, 指定@Cacheable(cacheNames = "keyName", keyGenerator = "myKeyGenerator")
 */
@Bean(name = "myKeyGenerator")
public KeyGenerator keyGenerator() {
    return new KeyGenerator() {
        @Override
        public Object generate(Object target, Method method, Object... params) {
            StringBuilder builder = new StringBuilder();
            builder.append(target.getClass().getName()); // 類名
            builder.append(method.getName()); // 方法名
            for (Object param : params) { // 參數
                builder.append(param);
            }
            return builder.toString();
        }
    };
}

errorHandler方法

添加自定義緩存異常處理,實現CacheErrorHandler接口即可。

Spring內置了SimpleCacheErrorHandler。

緩存僅僅是為了業務更快地查詢而存在的,如果因為緩存操作失敗導致正常的業務流程失敗,有點得不償失了。因此需要開發者自定義CacheErrorHandler處理緩存讀寫的異常。

/**
 * 當緩存讀寫異常時,忽略異常
 */
public class IgnoreExceptionCacheErrorHandler implements CacheErrorHandler {

    private static final Logger log = LoggerFactory.getLogger(IgnoreExceptionCacheErrorHandler.class);

    @Override
    public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
        log.error(exception.getMessage(), exception);
    }

    @Override
    public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
        log.error(exception.getMessage(), exception);
    }

    @Override
    public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
        log.error(exception.getMessage(), exception);
    }

    @Override
    public void handleCacheClearError(RuntimeException exception, Cache cache) {
        log.error(exception.getMessage(), exception);
    }
}

Spring的緩存注解

@Cacheable注解執行緩存

@Cacheable可用於修飾類或修飾方法,當使用@Cacheable修飾類時,用於告訴Spring在類級別上進行緩存 —— 程序調用該類的實例的任何方法時都需要緩存,而且共享同一個緩存區;當使用@Cacheable修飾方法時,用於告訴Spring在方法級別上進行緩存——只有當程序調用該方法時才需要緩存。

(1)類級別的緩存:使用@Cacheable修飾類時,就可控制Spring在類級別進行緩存,這樣當程序調用該類的任意方法時,只要傳入的參數相同,Spring就會使用緩存。

當程序第一次調用該類的實例的某個方法時,Spring緩存機制會將該方法返回的數據放入指定緩存區。以后程序調用該類的實例的任何方法時,只要傳入的參數相同,Spring將不會真正執行該方法,而是直接利用緩存區中的數據

(2)方法級別的緩存:使用@Cacheable修飾方法時,就可控制Spring在方法級別進行緩存,這樣當程序調用該方法時,只要傳入的參數相同,Spring就會使用緩存。

在 @Cacheable 注解的使用中,共有 9 個屬性供我們來使用,這 9 個屬性分別是: value、 cacheNames、 key、 keyGenerator、 cacheManager、 cacheResolver、 condition、 unless、 sync。

value/cacheNames 屬性

這兩個屬性代表的意義相同,根據@AliasFor注解就能看出來了。這兩個屬性都是用來指定緩存組件的名稱,即將方法的返回結果放在哪個緩存中,屬性定義為數組,可以指定多個緩存;

//這兩種配置等價
@Cacheable(value = "user") //@Cacheable(cacheNames = "user")
User getUser(Integer id);

key屬性

可以通過 key 屬性來指定緩存數據所使用的的 key,默認使用的是方法調用傳過來的參數作為 key。最終緩存中存儲的內容格式為:Entry<key,value> 形式。

(1) 如果請求沒有參數:key=new SimpleKey();

(2) 如果請求有一個參數:key=參數的值

(3) 如果請求有多個參數:key=new SimpleKey(params); (你只要知道 key不會為空就行了)

key值的編寫,可以使用 SpEL 表達式的方式來編寫;除此之外,我們同樣可以使用 keyGenerator 生成器的方式來指定 key,我們只需要編寫一個 keyGenerator ,將該生成器注冊到 IOC 容器即可。

名字 位置   描述 示例 

methodName

root object  當前被調用的方法名  #root.method.name 

method 

root object  當前被調用的方法  #root.methodName 

target 

root object  當前被調用的目標對象  #root.target 

targetClass 

root object  當前被調用的目標對象類  #root.targetClass 

args 

root object 當前被調用的方法的參數列表  #root.args[0] 
caches  root object  當前方法調用使用的緩存列表(如@Cacheable(value={“cache1”,“cache2”})),則有兩個cache  #root.caches[0].name 
argument name  evaluation context  方法參數的名字. 可以直接 #參數名 ,也可以使用 #p0或#a0 的形式,0代表參數的索引;  #id、#p0、#a0 
result  evaluation context  方法執行后的返回值(僅當方法執行之后的判斷有效,如’unless’、'cache put’的表達式 'cacheevict’的表達式beforeInvocation=false)  #result 

使用示例如下:

@Cacheable(value = "user",key = "#root.method.name")
User getUser(Integer id);

keyGenerator 屬性

key 的生成器。如果覺得通過參數的方式來指定比較麻煩,我們可以自己指定 key 的生成器的組件 id。key/keyGenerator屬性:二選一使用。我們可以通過自定義配置類方式,將 keyGenerator 注冊到 IOC 容器來使用。

@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
    return new KeyGenerator(){

        @Override
        public Object generate(Object target, Method method, Object... params) {
            return method.getName()+ Arrays.asList(params).toString();
        }
    };
}

cacheManager 屬性

該屬性,用來指定緩存管理器。針對不同的緩存技術,需要實現不同的 cacheManager,Spring 也為我們定義了如下的一些 cacheManger 實現:

CacheManger 描述
SimpleCacheManager 使用簡單的Collection來存儲緩存,主要用於測試
ConcurrentMapCacheManager 使用ConcurrentMap作為緩存技術(默認)
NoOpCacheManager 測試用
EhCacheCacheManager 使用EhCache作為緩存技術,以前在hibernate的時候經常用
GuavaCacheManager 使用google guava的GuavaCache作為緩存技術
HazelcastCacheManager 使用Hazelcast作為緩存技術
JCacheCacheManager 使用JCache標准的實現作為緩存技術,如Apache Commons JCS
RedisCacheManager 使用Redis作為緩存技術

cacheResolver 屬性

該屬性,用來指定緩存管理器。使用配置同 cacheManager 類似。(cacheManager指定管理器/cacheResolver指定解析器 它倆也是二選一使用)。

condition 屬性

條件判斷屬性,用來指定符合指定的條件下才可以緩存。也可以通過 SpEL 表達式進行設置。這個配置規則和上面表格中的配置規則是相同的。 

@Cacheable(value = "user",condition = "#id>0")//傳入的 id 參數值>0才進行緩存
User getUser(Integer id);
@Cacheable(value = "user",condition = "#a0>1")//傳入的第一個參數的值>1的時候才進行緩存
User getUser(Integer id);
@Cacheable(value = "user",condition = "#a0>1 and #root.methodName eq 'getUser'")//傳入的第一個參數的值>1 且 方法名為 getUser 的時候才進行緩存
User getUser(Integer id);

unless 屬性

unless屬性,意為"除非"的意思。即只有 unless 指定的條件為 true 時,方法的返回值才不會被緩存。可以在獲取到結果后進行判斷。

@Cacheable(value = "user",unless = "#result == null")//當方法返回值為 null 時,就不緩存
User getUser(Integer id);
@Cacheable(value = "user",unless = "#a0 == 1")//如果第一個參數的值是1,結果不緩存
User getUser(Integer id);

sync 屬性

該屬性用來指定是否使用異步模式,該屬性默認值為 false,默認為同步模式。異步模式指定 sync = true 即可,異步模式下 unless 屬性不可用。

@Cacheput注解執行緩存

在支持Spring Cache的環境下,對於使用@Cacheable標注的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的緩存元素,如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,否則才會執行並將返回結果存入指定的緩存中。

@CachePut也可以聲明一個方法支持緩存功能。與@Cacheable不同的是使用@CachePut標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。

@CachePut注解和@Cacheable注解,唯一不同的是:

  • @CachePut注解是先調用方法再將方法的返回值寫入緩存,方法體是一定會執行的
  • @Cacheable注解是先查詢緩存,if 緩存==null,就查詢數據庫,然后將方法的返回值寫入緩存

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

@CacheEvict注解清除緩存

@CacheEvict是用來標注在需要清除緩存元素的方法或類上的。

當標記在一個類上時表示所有的方法的執行都會觸發緩存的清除操作。

@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。

其中value、key和condition的語義與@Cacheable對應的屬性類似。

  • value表示清除操作是發生在哪些Cache上的(對應Cache的名稱);
  • key表示需要清除的是哪個key,如未指定則會使用默認策略生成的key;
  • condition表示清除操作發生的條件。

下面我們來介紹一下新出現的兩個屬性allEntries和beforeInvocation。

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

beforeInvocation屬性

清除操作默認是在對應方法成功執行之后觸發的,即方法如果因為拋出異常而未能成功返回時也不會觸發清除操作。使用beforeInvocation可以改變觸發清除操作的時間,當我們指定該屬性值為true時,Spring會在調用該方法之前清除緩存中的指定元素。

@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
    System.out.println("delete user by id: " + id);
}

@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) {
    return null;
}

使用自定義注解

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

@CacheConfig 公共的緩存配置

@CacheConfig 在類級別設置一些緩存相關的共同配置(與其它緩存配合使用)

@Cacheable的實現原理

Cacheable注解是典型的Spring AOP實現,在Spring里,aop可以簡單的理解為代理(AspectJ除外),我們聲明了@Cacheable的方法的類,都會被代理,在代理中,實現緩存的查詢與設置操作。

眾所周知,使用CgLib進行AOP動態代理是通過攔截器中的invoke方法執行的。

在這里找到了緩存方面的代理配置:

在配置中看到聲明了一個CacheInterceptor的攔截器。

它實現了MethodInterceptor接口並重寫其invoke方法。其中excute的主要邏輯在CacheAspectSupport中。 

在CacheAspectSupport中可以找到方法:

自定義緩存

使用EhCache

EhCache 是一個純Java的進程內緩存框架,具有快速、精干等特點,是Hibernate中默認CacheProvider。Ehcache是一種廣泛使用的開源Java分布式緩存。主要面向通用緩存,Java EE和輕量級容器。它具有內存和磁盤存儲,緩存加載器,緩存擴展,緩存異常處理程序,一個gzip緩存servlet過濾器,支持REST和SOAP api等特點。

Spring 提供了對緩存功能的抽象:即允許綁定不同的緩存解決方案(如Ehcache),但本身不直接提供緩存功能的實現。它支持注解方式使用緩存,非常方便。

特性:

  • 快速
  • 簡單
  • 多種緩存策略
  • 緩存數據有兩級:內存和磁盤,因此無需擔心容量問題
  • 緩存數據會在虛擬機重啟的過程中寫入磁盤
  • 可以通過RMI、可插入API等方式進行分布式緩存
  • 具有緩存和緩存管理器的偵聽接口
  • 支持多緩存管理器實例,以及一個實例的多個緩存區域
  • 提供Hibernate的緩存實現

1、pom配置

<!-- spring-data-redis -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>


<!-- ehcache -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.6</version>
</dependency>

2、在src/main/resources/創建一個配置文件 ehcache.xml

默認情況下Ehcache會自動加載classpath根目錄下名為ehcache.xml文件,也可以將該文件放到其他地方在使用時指定文件的位置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>

    <!-- 磁盤緩存位置 -->
    <diskStore path="D:\workspace\emptyPro\spring-study01\ehcache"/>

    <!-- 默認緩存 -->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxEntriesLocalDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>

    <!-- hello緩存 -->
    <cache name="hello"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="50"
           timeToLiveSeconds="50"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>

    <cache name="users"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="50"
           timeToLiveSeconds="50"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

3、創建一個配置類繼承CachingConfigurerSupport,重寫cacheManager方法,返回Ehcache的CacheManager管理器。

@EnableCaching
@Configuration
public class SpringCacheConfig extends CachingConfigurerSupport {

    @Resource(name = "ehcacheCacheManager")
    private CacheManager ehcacheCacheManager;

    @Override
    public CacheManager cacheManager() {
        return ehcacheCacheManager;
    }

    @Bean
    public CacheManager ehcacheCacheManager(){
        EhCacheCacheManager cacheManager = new EhCacheCacheManager();
        EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
        ClassPathResource resource = new ClassPathResource("ehcache.xml");
        ehCacheManagerFactoryBean.setConfigLocation(resource);
        cacheManager.setCacheManager(ehCacheManagerFactoryBean.getObject());
        return cacheManager;
    }
}

注:如果涉及到多個緩存管理器(如同時有redis和ehcache),重寫的cacheManager方法指定一個默認緩存管理器就行,其余緩存管理器只需用@Bean交給Spring IOC托管就行,然后在使用@Cacheable注解時通過cacheManager屬性指定。

Spring提供了CompositeCacheManager 。

使用Redis

1、pom配置

spring 5.0.2.RELEASE。

spring-data-redis 2.0.2.RELEASE

jedis 2.10.2

<!-- spring-data-redis (注意與redis的版本問題與spring的版本問題)-->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.0.2.RELEASE</version>
    <exclusions>

    </exclusions>
</dependency>
<!--2.9.1有連接泄露的bug-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.10.2</version>
</dependency>

2、配置RedisCacheManager

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;
import java.util.*;


@EnableCaching
@Configuration
public class SpringCacheConfig extends CachingConfigurerSupport {

    @Override
    public CacheManager cacheManager() {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory()),
                this.getRedisCacheConfigurationWithTtl(600), // 默認策略,未配置的 key 會使用這個
                this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }

    /**
     * 配置連接池
     *
     * @return
     */
    @Bean
    public JedisPoolConfig clusterPoolConfig() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        //連接池中最多可空閑鏈接個數,這里取值20,表示即使沒有用數據庫鏈接依然保持20個空閑鏈接
        poolConfig.setMaxIdle(20);
        //控制一個pool可以分配多少個jedis實例
        poolConfig.setMaxTotal(300);
        //最大等待時間,當沒有可用連接時,連接池等待鏈接被歸還的最大時間ms,超過時間就拋出異常
        poolConfig.setMaxWaitMillis(1000);
        //在獲取連接的時候檢查鏈接的有效性
        poolConfig.setTestOnCreate(true);
        return poolConfig;
    }

    /**
     * redis單機配置
     * RedisStandaloneConfiguration 單機
     * RedisSentinelConfiguration 主從復制(哨兵模式)
     * RedisClusterConfiguration 集群
     *
     * @return
     */
    @Bean
    public RedisClusterConfiguration configuration() {
        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
        clusterConfiguration.clusterNode("127.0.0.1", 6380);
        clusterConfiguration.clusterNode("127.0.0.1", 6381);
        clusterConfiguration.clusterNode("127.0.0.1", 6382);
        return clusterConfiguration;
    }

    /**
     * 配置連接工廠
     *
     * @return
     */
    @Bean
    public JedisConnectionFactory connectionFactory() {
        return new JedisConnectionFactory(configuration());
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap();
        //緩存設置,可以單獨為一個cache設置配置
        redisCacheConfigurationMap.put("users", this.getRedisCacheConfigurationWithTtl(3000));
        redisCacheConfigurationMap.put("usersAnother", this.getRedisCacheConfigurationWithTtl(18000));
        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }
}

3、測試驗證

UserService.java

public interface UserService {

    String getNameById(Long id);

    void deleteById(Long id);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    private static Map<Long, String> userMap = new HashMap();

    static {
        userMap.put(10001L, "張三");
        userMap.put(10002L, "李四");
    }

    @Cacheable(cacheNames = "users", key = "#id")
    @Override
    public String getNameById(Long id) {
        System.out.println("----查詢id=" + id);
        return userMap.get(id);
    }

    @CacheEvict(cacheNames = "users", key = "#id")
    @Override
    public void deleteById(Long id){
        System.out.println("清除:" + id);
    }
}

HelloController.java

@Controller
@RequestMapping("/hello")
public class HelloController {

    @Autowired
    private UserService userService;

    @ResponseBody
    @GetMapping("getNameById")
    public Map<String, Object> getNameById(Long id) {
        String name = userService.getNameById(id);
        Map<String, Object> result = new HashMap();
        result.put("name", name);
        return result;
    }

    @PostMapping("deleteById")
    public void deleteById(Long id){
        userService.deleteById(id);
    }
}

在postman訪問以下請求,觀察redis緩存的變化:

設置緩存 GET http://localhost:8888/hello/getNameById?id=10002

清除緩存 POST http://localhost:8888/hello/deleteById?id=10001

常見問題 

@Cacheable不生效的原因

@Cacheable注解中,一個方法A調同一個類里的另一個有緩存注解的方法B,這樣是不走緩存的。例如在同一個service里面兩個方法的調用,緩存是不生效的。

解決方案:

① 不使用注解的方式,直接取CacheManger 對象,把需要緩存的數據放到里面,類似於使用 Map,緩存的邏輯自己控制;或者可以使用redis的緩存方式去添加緩存;

② 把方法A和方法B放到兩個不同的類里面,例如:如果兩個方法都在同一個service接口里,把方法B放到另一個service里面,這樣在A方法里調B方法,就可以使用B方法的緩存。

③ 如果確實要在方法內部調用,要在aop的配置上加上exposeProxy=true,然后使用的時候使用AopContext.currentProxy() 代替this(不推薦)

為什么緩存沒有被正常創建

因為@Cacheable 是使用AOP 代理實現的 ,通過創建內部類來代理緩存方法,這樣就會導致一個問題,類內部的方法調用類內部的緩存方法不會走代理(因為方法之間的調用直接使用的是原始對象this,而非代理對象,因而內部調用不會產生代理),不會走代理,就不能正常創建緩存,所以每次都需要去調用數據庫。

注意點

因為@Cacheable 由AOP 實現,所以如果該方法被其它注解切入,當緩存命中的時候,則其它注解不能正常切入並執行,@Before 也不行,當緩存沒有命中的時候,其它注解可以正常工作

@Cacheable 方法不能進行內部調用,否則緩存無法創建。

 


免責聲明!

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



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