genericjackson2jsonredisserializer 会将 lua 返回的纯字符串(如 "test")当作 json 解析而报错,因其非合法 json 格式;推荐用 execute 原始字节数组重载绕过序列化链路。

GenericJackson2JsonRedisSerializer 会把 Lua 返回的纯字符串当 JSON 解析
这是最常见也最容易被忽略的根源。当你用 GenericJackson2JsonRedisSerializer 配置了 RedisTemplate 的 value 序列化器,它就会对所有从 Redis 读出的 value 字节数组尝试用 Jackson 做反序列化——不管这串字节是不是合法 JSON。
而 Lua 脚本返回的 "test" 是一个裸字符串,不是 JSON 字符串(JSON 字符串必须带双引号,即 "\"test\""),Jackson 解析时直接报 Unrecognized token 'test'。
同理,return 123 能过,是因为 Jackson 把数字字面量解析成 Long;return redis.call('get', 'key') 也能过,是因为 redis.call() 返回的是 Redis 内部对象,Lettuce 通常会把它转成 byte[] 或 String,但关键在于:如果这个值本身是 JSON 格式(比如之前存的就是 JSON),那刚好能被 Jackson 消化;否则就挂。
为什么不用 StringRedisTemplate?
很多开发者第一反应是“那我换 StringRedisTemplate 不就完了?”——确实能绕过这个问题,但它只解决“Lua 返回字符串”的场景,不解决本质矛盾:
-
StringRedisTemplate的 value 序列化器是StringRedisSerializer,它只做 UTF-8 编解码,不走 Jackson,所以不会报 JSON 解析错 - 但你项目里其他地方(比如缓存 User 对象)仍依赖
RedisTemplate+GenericJackson2JsonRedisSerializer,两者共存时容易混淆 key/value 类型,导致取错数据 - 更麻烦的是:如果你在 Lua 脚本里
return json.encode(...),而业务代码又用StringRedisTemplate去接,那就得自己手动new ObjectMapper().readValue(...),失去框架自动反序列化的便利
怎么让 Lua 返回值不触发 Jackson 反序列化?
核心思路是:让 Lua 脚本的执行结果绕过 RedisTemplate 的 value 序列化链路。有三种实操路径:
✅ 推荐方案:用 execute 方法的原始字节数组重载
byte[] rawResult = redisTemplate.execute(script, Collections.emptyList(), (RedisCallback<byte>) connection -> {
Object result = connection.eval(script.getScriptAsString().getBytes(), ReturnType.BULK_STRING, 0);
return (byte[]) result;
});
String luaOutput = new String(rawResult, StandardCharsets.UTF_8);
</byte>
⚠️ 注意:ReturnType.BULK_STRING 适用于返回字符串或 null;若脚本可能返回整数、数组等,需按实际类型选 ReturnType.INTEGER 或 ReturnType.MULTI,并做对应类型转换
✅ 替代方案:改用 StringRedisTemplate 执行 Lua,但统一约定所有 Lua 返回值都为字符串(含 JSON 字符串)
❌ 避免方案:在 Lua 里硬套 json.encode("test") —— 这会让返回变成 "\"test\"",虽然能被 Jackson 解析,但业务层拿到的是带双引号的字符串,语义污染严重
GenericJackson2JsonRedisSerializer 的 DefaultTyping 风险别踩
如果你真要用 GenericJackson2JsonRedisSerializer 并支持多类型反序列化(比如 Lua 返回的是 {"code":0,"msg":"ok"}),就必须配 ObjectMapper 的 enableDefaultTyping。但 Spring Boot 2.6+ 默认禁用它,且启用方式极容易翻车:
- 别用
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)—— 这等于开白名单通杀,有反序列化 RCE 风险 - 正确做法是注册白名单:用
SimpleModule显式添加你允许反序列化的类,再通过mapper.registerModule(module) - 更稳妥的是:Lua 脚本只返回简单结构(String/Long/Boolean),复杂结构走 JSON 字符串 + 手动
ObjectMapper.readValue(),把类型控制权收回到业务层
真正难处理的从来不是 Lua 脚本能返回什么,而是你让 Jackson 去猜它该还原成什么——只要没显式告诉 Jackson 类型,它就默认给你 LinkedHashMap。这个逻辑在 Lua 场景下尤其危险,因为脚本输出完全不受 Java 类型系统约束。











