Files
cn-rdms/rdms-framework/rdms-spring-boot-starter-redis
2026-03-11 19:32:37 +08:00
..
2026-03-11 19:32:37 +08:00
2026-03-11 19:32:37 +08:00
2026-03-11 19:32:37 +08:00

rdms-spring-boot-starter-redis

模块定位

rdms-spring-boot-starter-redis 不是单纯把 Redis 依赖引进来,而是把项目里常用的 Redis 使用约定统一收口。

当前模块主要承担三类职责:

  1. 统一 RedisTemplate 的序列化策略
  2. 统一 Spring Cache 基于 Redis 的实现方式
  3. 引入 Redisson为分布式锁、限流等上层模块提供基础能力

它的重点不是封装大量 Redis 工具类而是先把“Redis 怎么接入、怎么序列化、缓存怎么配置”这些基础设施统一好。

设计思路

1. 先统一 RedisTemplate再让上层模块复用

模块通过 RdmsRedisAutoConfiguration 注册了统一的 RedisTemplate<String, Object>,核心约定是:

  1. Key 使用字符串序列化
  2. Hash Key 使用字符串序列化
  3. Value 使用 JSON 序列化
  4. Hash Value 使用 JSON 序列化

这样可以避免各模块各自定义一套 RedisTemplate,导致同一个 Redis 里出现不同的序列化格式。

2. 优先解决“可读性和兼容性”问题

这个模块没有采用 JDK 默认序列化,而是直接使用 JSON 序列化。这样做的直接收益是:

  1. Redis 中的数据结构更容易观察和排查
  2. 避免 JDK 序列化带来的可读性差和兼容性问题
  3. 方便不同模块围绕同一种序列化格式工作

同时,它还额外处理了 LocalDateTime 的序列化问题,避免 Java 时间类型在 Redis 中读写异常。

3. 把 Cache 也纳入统一约定

模块不只提供 RedisTemplate,还把 Spring Cache 一起接入 Redis

  1. 开启 @EnableCaching
  2. 统一 RedisCacheConfiguration
  3. 统一 RedisCacheManager
  4. 扩展支持按缓存名声明过期时间

这说明模块的目标不是“给你 Redis 客户端自己玩”,而是先把项目里高频使用的缓存场景标准化。

4. 通过小扩展解决 Spring Cache 默认能力不够用的问题

模块里的 TimeoutRedisCacheManager 做了一个很实用的扩展:

  1. 如果缓存名是普通格式,例如 user
  2. 则按全局默认 TTL 工作
  3. 如果缓存名写成 user#10m
  4. 则这个缓存自动按 10 分钟过期

这相当于把“单个缓存项的过期时间”从配置文件和自定义代码里,收口到了缓存名约定里。

5. 给上层能力模块提供 Redis 和 Redisson 基座

这个模块本身不直接实现分布式锁、限流、幂等,但它提供了这些上层能力所依赖的基础能力:

  1. StringRedisTemplate
  2. RedisTemplate<String, Object>
  3. RedissonClient
  4. 基于 Redis 的 Spring Cache

因此像 rdms-spring-boot-starter-protectionrdms-spring-boot-starter-mq 这样的模块,都可以把 Redis 能力建立在这个 starter 之上。

自动装配链路

1. Spring Boot 自动配置入口

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

  1. com.njcn.rdms.framework.redis.config.RdmsRedisAutoConfiguration
  2. com.njcn.rdms.framework.redis.config.RdmsCacheAutoConfiguration

2. 各配置类负责的事情

配置类 作用
RdmsRedisAutoConfiguration 注册统一序列化规则的 RedisTemplate<String, Object>
RdmsCacheAutoConfiguration 开启 Spring Cache注册 RedisCacheConfiguration 和自定义 RedisCacheManager

核心组件

组件 作用
RdmsRedisAutoConfiguration 提供统一 JSON 序列化的 RedisTemplate并处理 LocalDateTime 序列化
RdmsCacheAutoConfiguration 统一 Redis Cache 前缀、TTL、空值缓存策略和 CacheManager
RdmsCacheProperties 提供 rdms.cache.* 配置项,目前包含 redis-scan-batch-size
TimeoutRedisCacheManager 支持通过 cacheName#ttl 的形式为单个缓存声明过期时间

功能说明

1. 统一 RedisTemplate 序列化

核心类:

  1. RdmsRedisAutoConfiguration

实现方式:

  1. RedisTemplate<String, Object> 统一由框架注册
  2. Key/Hash Key 使用字符串序列化
  3. Value/Hash Value 使用 JSON 序列化
  4. 通过 JavaTimeModule 兼容 LocalDateTime

适合场景:

  1. 业务模块直接注入 RedisTemplate<String, Object> 使用
  2. 需要缓存对象、列表、Map 等结构
  3. 希望 Redis 中的数据具有一定可读性

示例:

@Resource
private RedisTemplate<String, Object> redisTemplate;

public void saveUser(Long userId, UserRespVO user) {
    redisTemplate.opsForValue().set("user:" + userId, user);
}

2. 统一 Spring Cache 基于 Redis 的实现

核心类:

  1. RdmsCacheAutoConfiguration
  2. RdmsCacheProperties

实现方式:

  1. 开启 @EnableCaching
  2. 创建统一的 RedisCacheConfiguration
  3. spring.cache.redis.* 读取默认 TTL、前缀、空值缓存等配置
  4. 使用 JSON 序列化缓存值
  5. 使用自定义 RedisCacheManager

这个模块还额外做了一个约定:

  1. Cache Key 前缀使用单个 :,而不是默认的 ::
  2. 这样 Redis 中的 Key 更紧凑,也更方便在可视化工具中查看

Spring Cache 在这个模块里的作用,可以理解为:

  1. 让“方法查询结果缓存”变成注解式能力,而不是每次都手写 RedisTemplate
  2. 让缓存的写入、读取、失效围绕业务方法本身声明
  3. 让“查库 -> 放缓存 -> 下次命中缓存”这类标准流程交给框架处理

它更适合下面这类场景:

  1. 根据主键查询角色、菜单、模板、配置
  2. 根据某个唯一业务字段查询对象,例如 clientIdcode
  3. 根据某个参数查询相对稳定的数据,例如部门子节点列表、权限对应菜单列表

这类场景通常有几个共同点:

  1. 输入参数比较明确
  2. 返回结果可以直接缓存
  3. 数据更新时可以找到明确的缓存失效点

典型流程是:

  1. 在查询方法上加 @Cacheable
  2. 第一次执行时,方法正常查数据库或远程接口
  3. 返回结果自动写入 Redis
  4. 后续相同参数调用时,直接命中缓存,不再进入方法体
  5. 在更新、删除方法上通过 @CacheEvict 清理对应缓存

和直接使用 RedisTemplate 相比Spring Cache 的优势主要是:

  1. 不需要手写 get -> 判空 -> set -> expire 这类模板代码
  2. 缓存逻辑和业务方法绑定更紧,读代码时更容易看出哪里有缓存
  3. 适合“查询结果缓存”这种标准模式

当前项目里,权限、角色、菜单、模板这类数据就大量采用这种方式;而像 Token、验证码这类更像“直接把业务对象存到 Redis”的场景则更适合单独写 RedisDAO。

示例:

@Cacheable(cacheNames = "user", key = "#id")
public UserRespVO getUser(Long id) {
    return userApi.getUser(id);
}

如果某个写操作会影响缓存,需要配套清理缓存:

@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
    userMapper.deleteById(id);
}

在当前项目语境下,更贴近实际的使用案例包括:

  1. 根据角色 ID 查询角色信息,并在角色更新或删除时清理缓存
  2. 根据权限标识查询菜单 ID 列表,并在菜单新增、修改、删除时清理缓存
  3. 根据模板编码查询通知模板、邮件模板,并在模板变更时清理缓存

3. 支持按缓存名自定义过期时间

核心类:

  1. TimeoutRedisCacheManager

实现方式:

  1. 解析 @Cacheable(cacheNames = "...") 中的缓存名
  2. 如果命中 cacheName#ttl 格式,则为该缓存单独设置 TTL
  3. 支持的单位有:
    • d
    • h 小时
    • m 分钟
    • s
  4. 如果不带单位,默认按秒处理

示例:

@Cacheable(cacheNames = "user#10m", key = "#id")
public UserRespVO getUser(Long id) {
    return userApi.getUser(id);
}

说明:

  1. #10m 只用于声明这个缓存的过期时间是 10 分钟
  2. 它不会作为最终 Redis key 的一部分长期保留
  3. 如果 id = 1
  4. spring.cache.redis.key-prefix = rdms
  5. 那么实际 Redis key 一般形如 rdms:user:1
  6. 其中 user 是缓存名,1key = "#id" 计算出的缓存 Key
@Cacheable(cacheNames = "config#30s", key = "#key")
public ConfigRespVO getConfig(String key) {
    return configService.getConfig(key);
}

说明:

  1. 如果 key = "sms"
  2. spring.cache.redis.key-prefix = rdms
  3. 那么实际 Redis key 一般形如 rdms:config:sms
  4. #30s 同样只影响 TTL不直接体现在最终 key 名上

这种方式适合“绝大多数缓存共用默认 TTL少数缓存按场景单独缩短或延长过期时间”的场景。

补充说明:

  1. cacheNames = "user#10m" 中的 user 是缓存名,10m 是 TTL 描述
  2. key = "#id"key = "#key" 才决定具体缓存项的业务 Key
  3. 如果配置了 spring.cache.redis.key-prefix = rdms,最终 Redis key 通常是 rdms:user:1rdms:config:sms 这种形式
  4. #10m#30s 这类 TTL 后缀用于创建缓存时解析过期时间,不应理解成最终 key 名的一部分

4. 提供 Redisson 基础能力

从依赖上看,模块直接引入了 redisson-spring-boot-starter,因此除了 Spring Data Redis 以外,也会把 RedissonClient 这类能力带入项目。

这部分的直接收益是:

  1. 分布式锁能力可被上层模块直接复用
  2. 限流等基于 Redisson 的能力可直接建立在此模块之上
  3. 项目不需要在多个模块里重复引入 Redisson

如何使用

1. 引入依赖

业务模块通常直接依赖:

<dependency>
    <groupId>com.njcn</groupId>
    <artifactId>rdms-spring-boot-starter-redis</artifactId>
</dependency>

2. 基础配置

最常见的配置重点包括:

  1. spring.data.redis.*
  2. spring.cache.redis.*
  3. rdms.cache.redis-scan-batch-size

示例:

spring:
  data:
    redis:
      host: 127.0.0.1 # Redis 地址
      port: 6379 # Redis 端口
      database: 0 # 使用的 Redis 库编号

  cache:
    type: REDIS # Spring Cache 底层使用 Redis
    redis:
      time-to-live: 1h # 全局默认缓存过期时间
      cache-null-values: false # 是否缓存空值,通常建议关闭
      use-key-prefix: true # 是否启用缓存 Key 前缀
      key-prefix: rdms # 缓存 Key 的统一前缀

rdms:
  cache:
    redis-scan-batch-size: 100 # Redis CacheWriter 使用 SCAN 批量处理时的单次扫描数量

说明:

  1. spring.cache.redis.time-to-live 是全局默认缓存过期时间
  2. cacheName#ttl 可以覆盖单个缓存的 TTL
  3. rdms.cache.redis-scan-batch-size 用于控制 Redis CacheWriter 使用 SCAN 批量处理时的单次返回数量

3. 直接使用 RedisTemplate

如果场景不适合 Spring Cache例如

  1. 需要操作复杂数据结构
  2. 需要精细控制过期时间
  3. 需要手动删除、递增、集合操作

可以直接注入 RedisTemplate<String, Object>StringRedisTemplate 使用。

4. 优先使用 Spring Cache 的场景

如果场景是:

  1. 查询结果缓存
  2. 参数到结果的简单映射
  3. 希望通过注解快速声明缓存

则优先使用 @Cacheable@CacheEvict@CachePut,并利用统一的 Redis Cache 约定。

这三个注解的典型使用场景可以这样区分:

  1. @Cacheable 用于“先查缓存,缓存没有再执行方法,并把结果写入缓存”。 适合读操作,例如“根据 ID 查询角色”“根据编码查询模板”。
@Cacheable(cacheNames = "role", key = "#id", unless = "#result == null")
public RoleDO getRoleFromCache(Long id) {
    return roleMapper.selectById(id);
}
  1. @CacheEvict 用于“执行方法后删除缓存”,避免更新或删除数据后缓存还是旧值。 适合写操作,例如“更新角色后清理角色缓存”“删除模板后清理模板缓存”。
@CacheEvict(cacheNames = "role", key = "#updateReqVO.id")
public void updateRole(RoleSaveReqVO updateReqVO) {
    roleMapper.updateById(BeanUtils.toBean(updateReqVO, RoleDO.class));
}
  1. @CachePut 用于“执行方法后,把方法返回值直接写回缓存”。 适合更新后希望立即刷新缓存,而不是简单删除缓存的场景。
@CachePut(cacheNames = "role", key = "#result.id")
public RoleDO updateRoleAndReturn(RoleSaveReqVO updateReqVO) {
    RoleDO role = BeanUtils.toBean(updateReqVO, RoleDO.class);
    roleMapper.updateById(role);
    return role;
}

可以把它们简单记成:

  1. @Cacheable:查的时候用,没有缓存才执行方法
  2. @CacheEvict:改或删的时候用,执行后清缓存
  3. @CachePut:改的时候用,执行后直接刷新缓存

注意事项

  1. RedisTemplate<String, Object> 使用 JSON 序列化,适合对象缓存,但跨版本变更字段时仍应关注兼容性。
  2. LocalDateTime 已做序列化兼容处理,但复杂对象结构仍应关注 Jackson 序列化结果。
  3. cacheName#ttl 是这个模块扩展出来的约定,不是 Spring Cache 默认语法。
  4. 通过 cacheName#ttl 设置过期时间时TTL 是针对这个缓存名生效的,不是针对单条 Key 动态计算。
  5. RdmsCacheAutoConfiguration 将 Cache Key 前缀改成了单冒号 : 风格,如果已有依赖默认 :: 的脚本或习惯,需要注意。
  6. 这个模块本身主要解决“接入和约定”问题,不提供大量 Redis 业务工具方法;复杂 Redis 场景通常还是由业务模块或上层 starter 自己封装。

总结

这个模块的核心价值,不是简单引入 Redis 依赖,而是统一项目里的 Redis 使用方式:

  1. 统一 RedisTemplate 的序列化规则
  2. 统一 Spring Cache 的 Redis 落地方式
  3. 提供按缓存名声明 TTL 的扩展能力
  4. 为 Redisson 相关的上层能力提供基础环境

如果把它理解成一个“Redis 接入基座 + Cache 约定基座”的 starter这个模块的设计会比较清晰。