共计 8095 个字符,预计需要花费 21 分钟才能阅读完成。
上一篇博文介绍了 Spring 中缓存注解 @Cacheable
@CacheEvit
@CachePut
的根本应用,接下来咱们将看一下更高级一点的知识点
- key 生成策略
- 超时工夫指定
<!– more –>
I. 我的项目环境
1. 我的项目依赖
本我的项目借助 SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
+ redis5.0
进行开发
开一个 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> |
II. 扩大知识点
1. key 生成策略
对于 @Cacheable
注解,有两个参数用于组装缓存的 key
- cacheNames/value: 相似于缓存前缀
- key: SpEL 表达式,通常依据传参来生成最终的缓存 key
默认的redisKey = cacheNames::key
(留神两头的两个冒号)
如
/** | |
* 没有指定 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
如果没有参数,或者多个参数呢?
/** | |
* 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;} |
而后写一个测试 case
@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()
2. 自定义 key 生成策略
如果心愿应用自定义的 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" | |
} |
3. 缓存生效工夫
以上所有的缓存都没有设置生效工夫,理论的业务场景中,不设置生效工夫的场景有;但更多的都须要设置一个 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
的办法,其中有两个点
- 序列化形式:采纳 json 对缓存内容进行序列化
- 生效工夫:依据传参来设置生效工夫
如果心愿针对特定的 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 生效工夫
4. 自定义生效工夫扩大
尽管下面能够实现生效工夫指定,然而用起来仍然不是很爽,要么是全局设置为对立的生效工夫;要么就是在代码外面硬编码指定,生效工夫与缓存定义的中央隔离,这就很不直观了
接下来介绍一种,间接在注解中,设置生效工夫的 case
如上面的应用 case
/** | |
* 通过自定义的 RedisCacheManager, 对 value 进行解析,= 前面的示意生效工夫 | |
* @param key | |
* @return | |
*/ | |
@Cacheable(value = "ttl=30") | |
public String ttl(String key) {return "k_" + key;} |
自定义的策略如下:
- value 中,等号右边的为 cacheName, 等号左边的为生效工夫
要实现这个逻辑,能够扩大一个自定义的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; | |
} |
验证后果如下
5. 小结
到此基本上将 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
III. 不能错过的源码和相干知识点
0. 我的项目
系列博文
- Spring 系列缓存注解 @Cacheable @CacheEvit @CachePut 应用姿态介绍
源码
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 源码:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/
1. 一灰灰 Blog
尽信书则不如,以上内容,纯属一家之言,因集体能力无限,不免有疏漏和谬误之处,如发现 bug 或者有更好的倡议,欢送批评指正,不吝感谢
上面一灰灰的集体博客,记录所有学习和工作中的博文,欢送大家前去逛逛
- 一灰灰 Blog 集体博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top