上一篇博文介绍了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

@Beanpublic 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@Beanpublic 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