当前位置:   article > 正文

SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定原创_@cacheable key

@cacheable key

I. 项目环境

1. 项目依赖

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA + redis5.0进行开发

开一个 web 服务用于测试

  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.springframework.boot</groupId>
  4.         <artifactId>spring-boot-starter-web</artifactId>
  5.     </dependency>
  6.     <dependency>
  7.         <groupId>org.springframework.boot</groupId>
  8.         <artifactId>spring-boot-starter-data-redis</artifactId>
  9.     </dependency>
  10. </dependencies>

II. 扩展知识点

1. key 生成策略

对于@Cacheable注解,有两个参数用于组装缓存的 key

  • cacheNames/value: 类似于缓存前缀

  • key: SpEL 表达式,通常根据传参来生成最终的缓存 key

默认的redisKey = cacheNames::key (注意中间的两个冒号)

  1. /**
  2.  * 没有指定key时,采用默认策略 {@link org.springframework.cache.interceptor.SimpleKeyGenerator } 生成key
  3.  * <p>
  4.  * 对应的key为: k1::id
  5.  * value --> 等同于 cacheNames
  6.  * @param id
  7.  * @return
  8.  */
  9. @Cacheable(value = "k1")
  10. public String key1(int id) {
  11.     return "defaultKey:" + id;
  12. }

缓存 key 默认采用SimpleKeyGenerator来生成,比如上面的调用,如果id=1, 那么对应的缓存 key 为 k1::1

如果没有参数,或者多个参数呢?

  1. /**
  2.  * redis_key :  k2::SimpleKey[]
  3.  *
  4.  * @return
  5.  */
  6. @Cacheable(value = "k0")
  7. public String key0() {
  8.     return "key0";
  9. }
  10. /**
  11.  * redis_key :  k2::SimpleKey[id,id2]
  12.  *
  13.  * @param id
  14.  * @param id2
  15.  * @return
  16.  */
  17. @Cacheable(value = "k2")
  18. public String key2(Integer id, Integer id2) {
  19.     return "key1" + id + "_" + id2;
  20. }
  21. @Cacheable(value = "k3")
  22. public String key3(Map map) {
  23.     return "key3" + map;
  24. }

 

然后写一个测试 case

  1. @RestController
  2. @RequestMapping(path = "extend")
  3. public class ExtendRest {
  4.     @Autowired
  5.     private RedisTemplate redisTemplate;
  6.     @Autowired
  7.     private ExtendDemo extendDemo;
  8.     @GetMapping(path = "default")
  9.     public Map<StringObject> key(int id) {
  10.         Map<StringObject> res = new HashMap<>();
  11.         res.put("key0", extendDemo.key0());
  12.         res.put("key1", extendDemo.key1(id));
  13.         res.put("key2", extendDemo.key2(id, id));
  14.         res.put("key3", extendDemo.key3(res));
  15.         // 这里将缓存key都捞出来
  16.         Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
  17.             Set<byte[]> sets = connection.keys("k*".getBytes());
  18.             Set<String> ans = new HashSet<>();
  19.             for (byte[] b : sets) {
  20.                 ans.add(new String(b));
  21.             }
  22.             return ans;
  23.         });
  24.         res.put("keys", keys);
  25.         return res;
  26.     }
  27. }

访问之后,输出结果如下

  1. {
  2.     "key1""defaultKey:1",
  3.     "key2""key11_1",
  4.     "key0""key0",
  5.     "key3""key3{key1=defaultKey:1, key2=key11_1, key0=key0}",
  6.     "keys": [
  7.         "k2::SimpleKey [1,1]",
  8.         "k1::1",
  9.         "k3::{key1=defaultKey:1, key2=key11_1, key0=key0}",
  10.         "k0::SimpleKey []"
  11.     ]
  12. }

小结一下

  • 单参数:cacheNames::arg

  • 无参数: cacheNames::SimpleKey [], 后面使用 SimpleKey []来补齐

  • 多参数: cacheNames::SimpleKey [arg1, arg2...]

  • 非基础对象:cacheNames::obj.toString()

2. 自定义 key 生成策略

如果希望使用自定义的 key 生成策略,只需继承KeyGenerator,并声明为一个 bean

  1. @Component("selfKeyGenerate")
  2. public static class SelfKeyGenerate implements KeyGenerator {
  3.     @Override
  4.     public Object generate(Object target, Method methodObject... params) {
  5.         return target.getClass().getSimpleName() + "#" + method.getName() + "(" + JSON.toJSONString(params) + ")";
  6.     }
  7. }

然后在使用的地方,利用注解中的keyGenerator来指定 key 生成策略

  1. /**
  2.  * 对应的redisKey 为:get vv::ExtendDemo#selfKey([id])
  3.  *
  4.  * @param id
  5.  * @return
  6.  */
  7. @Cacheable(value = "vv", keyGenerator = "selfKeyGenerate")
  8. public String selfKey(int id) {
  9.     return "selfKey:" + id + " --> " + UUID.randomUUID().toString();
  10. }

测试用例

  1. @GetMapping(path = "self")
  2. public Map<StringObject> self(int id) {
  3.     Map<StringObject> res = new HashMap<>();
  4.     res.put("self", extendDemo.selfKey(id));
  5.     Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
  6.         Set<byte[]> sets = connection.keys("vv*".getBytes());
  7.         Set<String> ans = new HashSet<>();
  8.         for (byte[] b : sets) {
  9.             ans.add(new String(b));
  10.         }
  11.         return ans;
  12.     });
  13.     res.put("keys", keys);
  14.     return res;
  15. }

缓存 key 放在了返回结果的keys中,输出如下,和预期的一致

  1. {
  2.     "keys": [
  3.         "vv::ExtendDemo#selfKey([1])"
  4.     ],
  5.     "self""selfKey:1 --> f5f8aa2a-0823-42ee-99ec-2c40fb0b9338"
  6. }

3. 缓存失效时间

以上所有的缓存都没有设置失效时间,实际的业务场景中,不设置失效时间的场景有;但更多的都需要设置一个 ttl,对于 Spring 的缓存注解,原生没有额外提供一个指定 ttl 的配置,如果我们希望指定 ttl,可以通过RedisCacheManager来完成

  1. private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
  2.     // 设置 json 序列化
  3.     Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
  4.     ObjectMapper om = new ObjectMapper();
  5.     om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  6.     jackson2JsonRedisSerializer.setObjectMapper(om);
  7.     RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
  8.     redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
  9.             RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).
  10.             // 设置过期时间
  11.             entryTtl(Duration.ofSeconds(seconds));
  12.     return redisCacheConfiguration;
  13. }

 

上面是一个设置RedisCacheConfiguration的方法,其中有两个点

  • 序列化方式:采用 json 对缓存内容进行序列化

  • 失效时间:根据传参来设置失效时间

如果希望针对特定的 key 进行定制化的配置的话,可以如下操作

  1. private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
  2.     Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(8);
  3.     // 自定义设置缓存时间
  4.     // 这个k0 表示的是缓存注解中的 cacheNames/value
  5.     redisCacheConfigurationMap.put("k0", this.getRedisCacheConfigurationWithTtl(60 * 60));
  6.     return redisCacheConfigurationMap;
  7. }

最后就是定义我们需要的RedisCacheManager

  1. @Bean
  2. public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
  3.     return new RedisCacheManager(
  4.             RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
  5.             // 默认策略,未配置的 key 会使用这个
  6.             this.getRedisCacheConfigurationWithTtl(60),
  7.             // 指定 key 策略
  8.             this.getRedisCacheConfigurationMap()
  9.     );
  10. }

 

在前面的测试 case 基础上,添加返回 ttl 的信息

  1. private Object getTtl(String key) {
  2.     return redisTemplate.execute(new RedisCallback() {
  3.         @Override
  4.         public Object doInRedis(RedisConnection connection) throws DataAccessException {
  5.             return connection.ttl(key.getBytes());
  6.         }
  7.     });
  8. }
  9. @GetMapping(path = "default")
  10. public Map<StringObject> key(int id) {
  11.     Map<StringObject> res = new HashMap<>();
  12.     res.put("key0", extendDemo.key0());
  13.     res.put("key1", extendDemo.key1(id));
  14.     res.put("key2", extendDemo.key2(id, id));
  15.     res.put("key3", extendDemo.key3(res));
  16.     Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
  17.         Set<byte[]> sets = connection.keys("k*".getBytes());
  18.         Set<String> ans = new HashSet<>();
  19.         for (byte[] b : sets) {
  20.             ans.add(new String(b));
  21.         }
  22.         return ans;
  23.     });
  24.     res.put("keys", keys);
  25.     Map<StringObject> ttl = new HashMap<>(8);
  26.     for (String key : keys) {
  27.         ttl.put(key, getTtl(key));
  28.     }
  29.     res.put("ttl", ttl);
  30.     return res;
  31. }

返回结果如下,注意返回的 ttl 失效时间

 

4. 自定义失效时间扩展

虽然上面可以实现失效时间指定,但是用起来依然不是很爽,要么是全局设置为统一的失效时间;要么就是在代码里面硬编码指定,失效时间与缓存定义的地方隔离,这就很不直观了

接下来介绍一种,直接在注解中,设置失效时间的 case

如下面的使用 case

  1. /**
  2.  * 通过自定义的RedisCacheManager, 对value进行解析,=后面的表示失效时间
  3.  * @param key
  4.  * @return
  5.  */
  6. @Cacheable(value = "ttl=30")
  7. public String ttl(String key) {
  8.     return "k_" + key;
  9. }

自定义的策略如下:

  • value 中,等号左边的为 cacheName, 等号右边的为失效时间

要实现这个逻辑,可以扩展一个自定义的RedisCacheManager,如

  1. public class TtlRedisCacheManager extends RedisCacheManager {
  2.     public TtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
  3.         super(cacheWriter, defaultCacheConfiguration);
  4.     }
  5.     @Override
  6.     protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
  7.         String[] cells = StringUtils.delimitedListToStringArray(name, "=");
  8.         name = cells[0];
  9.         if (cells.length > 1) {
  10.             long ttl = Long.parseLong(cells[1]);
  11.             // 根据传参设置缓存失效时间
  12.             cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
  13.         }
  14.         return super.createRedisCache(name, cacheConfig);
  15.     }
  16. }

 

重写createRedisCache逻辑, 根据 name 解析出失效时间;

注册使用方式与上面一致,声明为 Spring 的 bean 对象

  1. @Primary
  2. @Bean
  3. public RedisCacheManager ttlCacheManager(RedisConnectionFactory redisConnectionFactory) {
  4.     return new TtlRedisCacheManager(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),
  5.             // 默认缓存配置
  6.             this.getRedisCacheConfigurationWithTtl(60));
  7. }

测试 case 如下

  1. @GetMapping(path = "ttl")
  2. public Map ttl(String k) {
  3.     Map<StringObject> res = new HashMap<>();
  4.     res.put("execute", extendDemo.ttl(k));
  5.     res.put("ttl"getTtl("ttl::" + k));
  6.     return res;
  7. }

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扩展解析

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/353151
推荐阅读
相关标签
  

闽ICP备14008679号