上一篇博文介紹了Spring中緩存註解@Cacheable
@CacheEvit
@CachePut
的基本使用,接下來咱們將看一下更高級一點的知識點java
<!-- more -->git
本項目藉助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
+ redis5.0
進行開發github
開一個web服務用於測試web
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>
對於@Cacheable
註解,有兩個參數用於組裝緩存的keyredis
默認的redisKey = cacheNames::key
(注意中間的兩個冒號)spring
如json
/** * 沒有指定key時,採用默認策略 {@link org.springframework.cache.interceptor.SimpleKeyGenerator } 生成key * <p> * 對應的key爲: k1::id * value --> 等同於 cacheNames * @param id * @return */ @Cacheable(value = "k1") public String key1(int id) { return "defaultKey:" + id; }
緩存key默認採用SimpleKeyGenerator
來生成,好比上面的調用,若是id=1
, 那麼對應的緩存key爲 k1::1
緩存
若是沒有參數,或者多個參數呢?app
/** * redis_key : k2::SimpleKey[] * * @return */ @Cacheable(value = "k0") public String key0() { return "key0"; } /** * redis_key : k2::SimpleKey[id,id2] * * @param id * @param id2 * @return */ @Cacheable(value = "k2") public String key2(Integer id, Integer id2) { return "key1" + id + "_" + id2; } @Cacheable(value = "k3") public String key3(Map map) { return "key3" + map; }
而後寫一個測試caseless
@RestController @RequestMapping(path = "extend") public class ExtendRest { @Autowired private RedisTemplate redisTemplate; @Autowired private ExtendDemo extendDemo; @GetMapping(path = "default") public Map<String, Object> key(int id) { Map<String, Object> res = new HashMap<>(); res.put("key0", extendDemo.key0()); res.put("key1", extendDemo.key1(id)); res.put("key2", extendDemo.key2(id, id)); res.put("key3", extendDemo.key3(res)); // 這裏將緩存key都撈出來 Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> { Set<byte[]> sets = connection.keys("k*".getBytes()); Set<String> ans = new HashSet<>(); for (byte[] b : sets) { ans.add(new String(b)); } return ans; }); res.put("keys", keys); return res; } }
訪問以後,輸出結果以下
{ "key1": "defaultKey:1", "key2": "key11_1", "key0": "key0", "key3": "key3{key1=defaultKey:1, key2=key11_1, key0=key0}", "keys": [ "k2::SimpleKey [1,1]", "k1::1", "k3::{key1=defaultKey:1, key2=key11_1, key0=key0}", "k0::SimpleKey []" ] }
小結一下
cacheNames::arg
cacheNames::SimpleKey []
, 後面使用 SimpleKey []
來補齊cacheNames::SimpleKey [arg1, arg2...]
cacheNames::obj.toString()
若是但願使用自定義的key生成策略,只需繼承KeyGenerator
,並聲明爲一個bean
@Component("selfKeyGenerate") public static class SelfKeyGenerate implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { return target.getClass().getSimpleName() + "#" + method.getName() + "(" + JSON.toJSONString(params) + ")"; } }
而後在使用的地方,利用註解中的keyGenerator
來指定key生成策略
/** * 對應的redisKey 爲: get vv::ExtendDemo#selfKey([id]) * * @param id * @return */ @Cacheable(value = "vv", keyGenerator = "selfKeyGenerate") public String selfKey(int id) { return "selfKey:" + id + " --> " + UUID.randomUUID().toString(); }
測試用例
@GetMapping(path = "self") public Map<String, Object> self(int id) { Map<String, Object> res = new HashMap<>(); res.put("self", extendDemo.selfKey(id)); Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> { Set<byte[]> sets = connection.keys("vv*".getBytes()); Set<String> ans = new HashSet<>(); for (byte[] b : sets) { ans.add(new String(b)); } return ans; }); res.put("keys", keys); return res; }
緩存key放在了返回結果的keys
中,輸出以下,和預期的一致
{ "keys": [ "vv::ExtendDemo#selfKey([1])" ], "self": "selfKey:1 --> f5f8aa2a-0823-42ee-99ec-2c40fb0b9338" }
以上全部的緩存都沒有設置失效時間,實際的業務場景中,不設置失效時間的場景有;但更多的都須要設置一個ttl,對於Spring的緩存註解,原生沒有額外提供一個指定ttl的配置,若是咱們但願指定ttl,能夠經過RedisCacheManager
來完成
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) { // 設置 json 序列化 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; }
上面是一個設置RedisCacheConfiguration
的方法,其中有兩個點
若是但願針對特定的key進行定製化的配置的話,能夠以下操做
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() { Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(8); // 自定義設置緩存時間 // 這個k0 表示的是緩存註解中的 cacheNames/value redisCacheConfigurationMap.put("k0", this.getRedisCacheConfigurationWithTtl(60 * 60)); return redisCacheConfigurationMap; }
最後就是定義咱們須要的RedisCacheManager
@Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return new RedisCacheManager( RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), // 默認策略,未配置的 key 會使用這個 this.getRedisCacheConfigurationWithTtl(60), // 指定 key 策略 this.getRedisCacheConfigurationMap() ); }
在前面的測試case基礎上,添加返回ttl的信息
private Object getTtl(String key) { return redisTemplate.execute(new RedisCallback() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { return connection.ttl(key.getBytes()); } }); } @GetMapping(path = "default") public Map<String, Object> key(int id) { Map<String, Object> res = new HashMap<>(); res.put("key0", extendDemo.key0()); res.put("key1", extendDemo.key1(id)); res.put("key2", extendDemo.key2(id, id)); res.put("key3", extendDemo.key3(res)); Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> { Set<byte[]> sets = connection.keys("k*".getBytes()); Set<String> ans = new HashSet<>(); for (byte[] b : sets) { ans.add(new String(b)); } return ans; }); res.put("keys", keys); Map<String, Object> ttl = new HashMap<>(8); for (String key : keys) { ttl.put(key, getTtl(key)); } res.put("ttl", ttl); return res; }
返回結果以下,注意返回的ttl失效時間
雖然上面能夠實現失效時間指定,可是用起來依然不是很爽,要麼是全局設置爲統一的失效時間;要麼就是在代碼裏面硬編碼指定,失效時間與緩存定義的地方隔離,這就很不直觀了
接下來介紹一種,直接在註解中,設置失效時間的case
以下面的使用case
/** * 經過自定義的RedisCacheManager, 對value進行解析,=後面的表示失效時間 * @param key * @return */ @Cacheable(value = "ttl=30") public String ttl(String key) { return "k_" + key; }
自定義的策略以下:
要實現這個邏輯,能夠擴展一個自定義的RedisCacheManager
,如
public class TtlRedisCacheManager extends RedisCacheManager { public TtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { super(cacheWriter, defaultCacheConfiguration); } @Override protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) { String[] cells = StringUtils.delimitedListToStringArray(name, "="); name = cells[0]; if (cells.length > 1) { long ttl = Long.parseLong(cells[1]); // 根據傳參設置緩存失效時間 cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl)); } return super.createRedisCache(name, cacheConfig); } }
重寫createRedisCache
邏輯, 根據name解析出失效時間;
註冊使用方式與上面一致,聲明爲Spring的bean對象
@Primary @Bean public RedisCacheManager ttlCacheManager(RedisConnectionFactory redisConnectionFactory) { return new TtlRedisCacheManager(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory), // 默認緩存配置 this.getRedisCacheConfigurationWithTtl(60)); }
測試case以下
@GetMapping(path = "ttl") public Map ttl(String k) { Map<String, Object> res = new HashMap<>(); res.put("execute", extendDemo.ttl(k)); res.put("ttl", getTtl("ttl::" + k)); return res; }
驗證結果以下
到此基本上將Spring中緩存註解的經常使用姿式都介紹了一下,不管是幾個註解的使用case,仍是自定義的key策略,失效時間指定,單純從使用的角度來看,基本能知足咱們的平常需求場景
下面是針對緩存註解的一個知識點抽象
緩存註解
@Cacheable
: 緩存存在,則從緩存取;不然執行方法,並將返回結果寫入緩存@CacheEvit
: 失效緩存@CachePut
: 更新緩存@Caching
: 都註解組合配置參數
cacheNames/value
: 能夠理解爲緩存前綴key
: 能夠理解爲緩存key的變量,支持SpEL表達式keyGenerator
: key組裝策略condition/unless
: 緩存是否可用的條件默認緩存ke策略y
下面的cacheNames爲註解中定義的緩存前綴,兩個分號固定
cacheNames::arg
cacheNames::SimpleKey []
, 後面使用 SimpleKey []
來補齊cacheNames::SimpleKey [arg1, arg2...]
cacheNames::obj.toString()
緩存失效時間
失效時間,本文介紹了兩種方式,一個是集中式的配置,經過設置RedisCacheConfiguration
來指定ttl時間
另一個是擴展RedisCacheManager
類,實現自定義的cacheNames
擴展解析
Spring緩存註解知識點到此告一段落,我是一灰灰,歡迎關注長草的公衆號一灰灰blog
系列博文
源碼
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛