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

这篇具有很好参考价值的文章主要介绍了SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

spring cache spel 定义key,spring boot,缓存,spring,java,程序人生

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;
}

 文章来源地址https://www.toymoban.com/news/detail-827408.html

然后写一个测试 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 失效时间

spring cache spel 定义key,spring boot,缓存,spring,java,程序人生 

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;
}

spring cache spel 定义key,spring boot,缓存,spring,java,程序人生

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

 

到了这里,关于SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring 中的 @Cacheable 缓存注解,太好用了!

    第一个问题,首先要搞明白什么是缓存,缓存的意义是什么。 对于普通业务,如果要查询一个数据,一般直接select数据库进行查找。但是在高流量的情况下,直接查找数据库就会成为性能的瓶颈。因为数据库查找的流程是先要从磁盘拿到数据,再刷新到内存,再返回数据。磁

    2024年02月16日
    浏览(40)
  • @EnableCaching @Cacheable @CachePut redis注解缓存

    @EnableCaching注解是spring framework中的注解驱动的缓存管理功能。自spring版本3.1起加入了该注解。如果你使用了这个注解,那么你就不需要在XML文件中配置cache manager了。 当你在配置类(@Configuration)上使用@EnableCaching注解时,会触发一个post processor,这会扫描每一个spring bean,查看是

    2024年02月13日
    浏览(55)
  • SpringMVC之自定义注解

    目录 前言 一、自定义注解 1.Java注解简介 2. 注解的用处 3. 为什么要用注解 4.自定义注解的应用场景 5. 注解的分类 6.如何定义并使用自定义注解 7.自定义注解三种使用案例 案例一: 案例二: 案例三: 二、Aop自定义注解的应用   1 .自定义注解类 2.切面类 3.Controller层 随着We

    2024年02月07日
    浏览(51)
  • 一文掌握SpringBoot注解之@Cacheable 知识文集(1)

    🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论⭐收藏 🔎 SpringBoot 领域知识 🔎 链接 专栏 SpringBoot 专业知识学习一 SpringBoot专栏 Sprin

    2024年01月20日
    浏览(45)
  • SpringBoot 缓存之 @Cacheable 详细介绍

    1、缓存介绍 Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。 其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方

    2024年04月27日
    浏览(35)
  • 【springboot】缓存之@Cacheable、@CachePut、@CacheEvict的用法

    一、注解参数说明 1.1 属性说明 1.1.1 value/cacheNames 属性 1.这两个属性代表的意义相同 2.用来指定缓存组件的名称,将方法的返回结果存进缓存的名称 3.定义为数组时,可以存到多个缓存key中 1.1.2 key属性 1.指定缓存数据的key 2.redis作为缓存时,redis中的key为value::key 3.方法没有参

    2024年02月06日
    浏览(58)
  • SpringBoot之自定义starter

    目录 一、什么是SpringBoot starter机制 二、为什么要自定义starter 三、什么时候需要创建自定义starter 四、自动加载核心注解说明 五、自定义starter的开发流程 案例一:为短信发送功能创建一个starter 案例二:AOP方式统一服务日志 SpringBoot中的starter是一种非常重要的机制(自动化配

    2024年02月01日
    浏览(42)
  • SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势

    虽然 http 的提供了一整套完整、定义明确的状态码,但实际的业务支持中,后端并不总会遵守这套规则,更多的是在返回结果中,加一个 code 字段来自定义业务状态,即便是后端 5xx 了,返回给前端的 http code 依然是 200 那么如果我想遵守 http 的规范,不同的 case 返回不同的

    2024年04月12日
    浏览(36)
  • 根据aop实现自定义缓存注解

    自定义注解 切面 使用

    2024年02月13日
    浏览(55)
  • Redis(概述、应用场景、线程模式、数据持久化、数据一致、事务、集群、哨兵、key过期策略、缓存穿透、击穿、雪崩)

    目录 Redis概述 应用场景 Redis的线程模式 数据持久化 1.Rdb(Redis DataBase) 2.Aof(Append Only File) mysql与redis保持数据一致 redis事务 主从复制(Redis集群) 哨兵模式 key过期策略 缓存穿透、击穿、雪崩 1.缓存穿透:缓存中没有,在mysql中也没有 2.缓存击穿:数据在数据库中存在,某个

    2024年01月16日
    浏览(64)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包