RedisMessageListenerContainer 必须手动声明 Bean,Spring Boot 2.0+ 不自动配置;监听器需实现 MessageListener 并通过 addMessageListener() 注册,不可用 @EventListener;序列化需与发布端一致,推荐统一使用 StringRedisTemplate。
Spring Boot 2.0+ 默认不自动配置 RedisMessageListenerContainer,哪怕你引入了 spring-boot-starter-data-redis 并配好了 RedisConnectionFactory。不显式定义这个 Bean,@EventListener 或监听器类根本不会被注册,订阅行为静默失效。
常见错误现象:RedisMessageListenerContainer 没有启动日志(如 “Starting RedisMessageListenerContainer”),发布消息后监听器无任何响应,也没有报错。
正确做法是手动声明 Bean,并确保它依赖已配置的 RedisConnectionFactory:
@Beanpublic RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.setTaskExecutor(new ThreadPoolTaskExecutor()); // 避免阻塞主线程 return container;}
Spring 的 @EventListener 只适用于 Spring 事件(ApplicationEvent),对 Redis 发布/订阅消息完全无效。必须通过 RedisMessageListenerContainer.addMessageListener() 显式注册监听器实例。
典型误区:写一个带 @Component 和 @EventListener 的类,以为能收到 Redis 消息——结果永远收不到。
实操建议:
MessageListener 接口(或继承 KeyExpirationEventMessageListener 等子类)PatternTopic 订阅通配符频道(如 PatternTopic("user:*")),用 ChannelTopic 订阅精确频道(如 ChannelTopic("user.login"))container.addMessageListener(myListener, new ChannelTopic("order.created"))
默认 RedisMessageListenerContainer 使用 JdkSerializationRedisSerializer,但如果你的生产者用的是 StringRedisTemplate(即 StringRedisSerializer),两边序列化方式不一致,onMessage(Message, byte[]) 中的 message.getBody() 会是乱码或空字节数组。
解决方案取决于你的使用场景:
StringRedisTemplate:配置容器时设置 setTopicSerializer(new StringRedisSerializer())(注意:该方法仅在 Spring Data Redis 2.6+ 可用)MessageListener,在 onMessage() 内手动用 new String(message.getBody(), StandardCharsets.UTF_8) 解码RedisTemplate 和 StringRedisTemplate 对同一组频道收发消息不能用 execute() + 原生 PUBLISH 命令绕过序列化逻辑,否则监听器收到的消息体无法被反序列化(尤其当容器配置了非默认序列化器时)。
正确发布方式只有两个:
redisTemplate.convertAndSend("channel.name", "hello") —— 要求 redisTemplate 的 value serializer 与监听器解码方式匹配stringRedisTemplate.convertAndSend("channel.name", "hello") —— 最简单,推荐用于纯字符串消息容易忽略的一点:convertAndSend() 是异步非阻塞的,但若连接池耗尽或 Redis 不可用,它会抛出 RedisConnectionFailureException,需在外层捕获并处理,而不是假设“发了就一定到”。