You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot响应式控制器方法使用@Cacheable注解集成Redis缓存的异常问题及解决方案咨询

解决Spring Boot响应式方法Redis缓存序列化异常的方案

这个问题我之前在响应式项目里踩过坑,核心原因很明确:Spring Cache默认会直接缓存方法的返回值——也就是你代码里的Mono<Product>,但Redis的默认序列化器只支持实现Serializable的类型,Mono显然不满足这个要求,所以才抛出了那个异常。下面是两种推荐的解决方式,你可以根据自己的场景选择:

方案一:使用Spring官方的响应式缓存支持

从Spring Boot 2.2版本开始,框架对响应式类型(Mono/Flux)的缓存有了原生支持,只需要配置对应的响应式缓存管理器即可,不需要大幅修改现有代码:

  1. 配置ReactiveRedisCacheManager
    创建一个自定义的缓存管理器Bean,替换默认的同步版本,同时指定支持JSON序列化的序列化器(避免要求实体类实现Serializable):

    @Configuration
    public class ReactiveRedisCacheConfig {
        @Bean
        public ReactiveRedisCacheManager reactiveRedisCacheManager(ReactiveRedisConnectionFactory connectionFactory) {
            RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
            
            return ReactiveRedisCacheManager.builder(connectionFactory)
                    .cacheDefaults(cacheConfig)
                    .build();
        }
    }
    
  2. 修正并保留原有的@Cacheable注解
    你的原方法代码可以基本不变,但要修正@Cacheable的key表达式(因为方法参数名是sku,原来的#id会导致SpEL表达式找不到变量):

    @Cacheable(value = "products", key = "#sku")
    @GetMapping(value = "/product/{id}")
    public Mono<Product> getProduct(@PathVariable("id") String sku) { 
        // 你的业务逻辑代码
    }
    

    配置完成后,框架会自动识别返回的Mono类型,订阅它拿到实际的Product对象后再进行缓存,而不是缓存Mono本身。

方案二:手动处理响应式缓存逻辑

如果需要更灵活的缓存控制(比如自定义过期时间、缓存失效策略),可以直接使用ReactiveRedisTemplate手动实现缓存的读写逻辑:

@RestController
public class ProductController {
    @Autowired
    private ReactiveRedisTemplate<String, Product> reactiveRedisTemplate;
    // 假设你有一个获取Product的业务服务
    @Autowired
    private ProductService productService;

    @GetMapping(value = "/product/{id}")
    public Mono<Product> getProduct(@PathVariable("id") String sku) {
        String cacheKey = "products:" + sku;
        // 先从缓存读取
        return reactiveRedisTemplate.opsForValue().get(cacheKey)
                // 如果缓存为空,执行原业务逻辑并写入缓存
                .switchIfEmpty(Mono.defer(() -> productService.getProductById(sku)
                        .doOnNext(product -> 
                                // 写入缓存,这里可以自定义过期时间,比如1小时
                                reactiveRedisTemplate.opsForValue().set(cacheKey, product, Duration.ofHours(1)).subscribe()
                        )
                ));
    }
}

这种方式完全绕过了Spring Cache的自动处理,直接操作实际的Product对象,从根源上避免了缓存Mono的问题,同时也能更精细地控制缓存行为。

额外注意事项

  • 不管用哪种方案,都要保证Product对象可以被正确序列化:推荐使用GenericJackson2JsonRedisSerializer(方案一中的配置),它不需要实体类实现Serializable,而且序列化后的内容是可读的JSON格式,方便调试。
  • 如果坚持用JDK序列化器,那你的Product类必须实现Serializable接口,同时要注意关联对象的序列化问题。

内容的提问来源于stack exchange,提问作者kayelbb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 23:22:35