使用 Spring Cache 能夠極大的簡化咱們對數據的緩存,而且它封裝了多種緩存,本文基於 redis 來講明。html
一、所需依賴java
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
二、配置文件redis
spring: # redis鏈接信息 redis: host: 192.168.56.10 port: 6379 cache: # 指定使用的緩存類型 type: redis # 過時時間 redis: time-to-live: 3600000 # 是否開啓前綴,默認爲true use-key-prefix: true # 鍵的前綴,若是不配置,默認就是緩存名cacheNames key-prefix: CACHE_ # 是否緩存空置,防止緩存穿透,默認爲true cache-null-values: true
三、Spring Cache 提供的註解以下,使用方法參見:官方文檔,經過這些註解,咱們能夠方便的操做緩存數據。spring
@Cacheable
:觸發緩存寫入的操做@CacheEvict
:觸發緩存刪除的操做@CachePut
:更新緩存,而不會影響方法的執行@Caching
:從新組合要應用於一個方法的多個緩存操做,即對一個方法添加多個緩存操做@CacheConfig
:在類級別共享一些與緩存有關的常見設置例如,若是須要對返回結果進行緩存,直接在方法上標註 @Cacheable
註解緩存
@Cacheable(cacheNames = "userList") //指定緩存的名字,便於區分不一樣緩存 public List<User> getUserList() { ... }
四、redis 默認使用 jdk 序列化,須要咱們配置序列化機制,自定義一個配置類,不然存入的數據顯示亂碼app
@EnableCaching //開啓緩存 @Configuration public class MyCacheConfig { @Bean public RedisCacheConfiguration redisCacheConfiguration(){ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); //指定鍵和值的序列化機制 config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return config; } }
五、使用以上配置後,雖然亂碼的問題解決了,但配置文件又不生效了,好比過時時間等,這是由於在初始化時會判斷用戶是否自定義了配置文件,若是自定義了,原來的就不會生效,源碼以下:ide
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(ClassLoader classLoader) { //若是配置了,就返回自定義的配置 if (this.redisCacheConfiguration != null) { return this.redisCacheConfiguration; } //沒配置使用默認的配置 Redis redisProperties = this.cacheProperties.getRedis(); org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration .defaultCacheConfig(); config = config.serializeValuesWith( SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader))); if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixKeysWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; }
六、因此,咱們也須要手動獲取 ttl、prefix 等屬性,直接仿照源碼就行,將配置類修改成以下:spring-boot
@EnableCaching //開啓緩存 @Configuration @EnableConfigurationProperties(CacheProperties.class) //緩存的全部配置屬性都在這個類裏 public class MyCacheConfig { @Bean public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) { //獲取默認配置 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); //指定鍵和值的序列化機制 config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); //獲取配置文件的配置 CacheProperties.Redis redisProperties = cacheProperties.getRedis(); if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixKeysWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; } }
在 Spring 中 CacheManager 負責建立管理 Cache,Cache 負責緩存的讀寫,所以使用 redis 做爲緩存對應的就有 RedisCacheManager 和 RedisCache。this
打開 RedisCache 源碼,咱們須要注意這兩個方法:code
一、讀取數據,未加鎖
@Override protected Object lookup(Object key) { byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key)); if (value == null) { return null; } return deserializeCacheValue(value); }
二、讀取數據,加鎖,這是 RedisCache 中惟一一個同步方法
@Override public synchronized <T> T get(Object key, Callable<T> valueLoader) { ValueWrapper result = get(key); if (result != null) { return (T) result.get(); } T value = valueFromLoader(key, valueLoader); put(key, value); return value; }
經過打斷點的方式能夠知道 RedisCache 默認調用的是 lookup(),所以不能應對緩存穿透,若是有相關需求,能夠這樣配置:@Cacheable(sync = true)
,開啓同步模式,此配置只在 @Cacheable
中才有。
Spring Cache 對於讀模式下緩存失效的解決方案:
cache-null-values: true
,容許寫入空值@Cacheable(sync = true)
,加鎖time-to-live:xxx
,設置不一樣的過時時間而對於寫模式,Spring Cache 並無相應處理,咱們須要使用其它方式處理。
總的來講:
一、對於常規數據(讀多寫少,及時性、一致性要求不高的數據)徹底能夠使用 Spring Cache
二、對於特殊數據(好比要求高一致性)則須要特殊處理