DDK Redis Starter
提供一个配好 JSON 序列化的
RedisTemplate<String, Object>,省掉每个项目重复写的那段序列化配置;反序列化只放行白名单里的类型。
能做什么
- 注册
RedisTemplate<String, Object>:key / hashKey 用字符串序列化,value / hashValue 用GenericJacksonJsonRedisSerializer(Jackson 3),内部JsonMapper由RedisJsonMapper构造:- 按字段存取(字段可见、getter 不可见):领域对象不一定有 getter,getter 里也可能带计算逻辑;
- 类型信息写进
@class属性:除 JDK 自带的 final 标量(String、Long等)、枚举、JsonNode外都写类名,record 也能读回原类型(NON_FINAL做不到这点,record 是 final 的); - 反序列化白名单:用
BasicPolymorphicTypeValidator只放行三类包里的类型——- JDK 常用包:
java.lang、java.util、java.time、java.math(java.io、java.net这类能触发 I/O 的包刻意不在内); - 应用根包:
@SpringBootApplication所在包(取自AutoConfigurationPackages),通常不用任何配置就能覆盖业务 DTO; ddk.redis.trusted-packages显式补充的包。
- JDK 常用包:
- 其余类型在读取时直接抛
SerializationException。任何能写 Redis 的人都无法再借@class让应用实例化 classpath 上的任意类; findAndAddModules()注册JavaTimeModule等模块,日期写成 ISO 字符串,忽略未知字段。
- 不提供
RedisUtil之类的工具类:早期版本有过一个逐个方法转发RedisTemplate的封装,覆盖面不全(没有 ZSet、scan、pipeline、Lua),还把可能为null的包装类型原样透传,已移除。直接注入RedisTemplate<String, Object>使用opsForValue()/opsForHash()等即可。 - 依赖聚合:
spring-boot-starter-data-redis(Lettuce 客户端)、spring-boot-starter-json、ddk-core。- 连接工厂、连接池、
StringRedisTemplate仍来自 Boot 的RedisAutoConfiguration,本 starter 只接管redisTemplate这一个 Bean。
- 连接工厂、连接池、
text
写入 User(record) ──► {"@class":"com.example.app.User","id":1,...}
读取 @class 在白名单内? ── 是 ──► User
└─ 否 ──► SerializationException(不实例化)引入方式
xml
<dependency>
<groupId>com.ddk</groupId>
<artifactId>ddk-redis-starter</artifactId>
<version>${ddk.version}</version>
</dependency>import 了 ddk-dependencies BOM 之后 <version> 可以省略(见快速开始)。
配置项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ddk.redis.trusted-packages | List<String> | 空 | 额外允许反序列化的包,按前缀匹配。JDK 常用包和应用根包已默认放行,这里只补充此外的包,例如独立模块里的共享 DTO |
spring.data.redis.* | - | Boot 默认 | 连接配置,由 Boot 的 RedisProperties 承载 |
spring.jackson.* | - | — | 对本 starter 无效。Redis 的 ObjectMapper 需要类型信息和白名单,与 Web 层的 JSON 行为本就不同,刻意不共用 |
yaml
ddk:
redis:
trusted-packages:
- com.example.shared.dto装配条件与降级行为
- 自动配置类:
com.ddk.redis.starter.config.DdkRedisAutoConfiguration,@AutoConfiguration(before = RedisAutoConfiguration.class)——排在 Boot 之前,保证同名的redisTemplate由这里提供,而不是被 Boot 的 JDK 序列化版本抢先注册。 - 条件:
@ConditionalOnClass(RedisTemplate.class);redisTemplate:@ConditionalOnMissingBean(name = "redisTemplate"),应用自己定义同名 Bean 即可整体替换;redisUtil:@ConditionalOnMissingBean。
- Redis 服务不可达不会导致启动失败(Lettuce 懒连接),但每次读写都会抛
RedisConnectionFailureException并传播给调用方,没有降级或熔断。
不适用的场景 / 已知问题
- 类名写进了数据。类改名、换包后,Redis 里的老数据读不回来;跨服务共享同一个 key 时,读方必须有同名类。需要跨语言或长期存储的数据,应改用明确值类型的序列化器。按缓存名绑定值类型的方案见缓存设计推演。
- 标量不带类型信息。
Long小值读回来是Integer,Float读回来是Double;取数值时按Number处理。 - 不可变集合读不回来。
List.of()的实现类是java.util.ImmutableCollections$ListN,虽在白名单内,但没有可用的构造方式;存集合时用ArrayList/HashMap。 RedisTemplate的返回值是可能为null的包装类型(Boolean hasKey、Long increment等),自动拆箱有 NPE 风险。- 没有分布式锁与限流,需要时自己实现或引 Redisson。
- 与 DDK Cache Starter 互不影响:缓存走
RedisCacheManager自己的序列化配置,同一份数据在@Cacheable和redisTemplate之间不能互读。