在 Spring Boot 中配置 Redis 发布订阅功能时,`RedisMessageListenerContainer` 必须手动声明为 Bean,Spring Boot 2.0 及以上版本不会自动完成该配置;监听器需要实现 `MessageListener` 接口,并通过 `addMessageListener()` 方法手工注册,不能依赖 `@EventListener` 注解;消息序列化策略必须与发布端保持一致,最稳妥的方式是统一使用 `StringRedisTemplate`。

RedisMessageListenerContainer 配置必须手动声明 Bean
Spring Boot 2.0 及更高版本默认不会自动创建 `RedisMessageListenerContainer` —— 即使你正确引入了 `spring-boot-starter-data-redis` 依赖,并且配置了 `RedisConnectionFactory`,容器也不会自动运行。如果不显式声明一个 Bean,那么 `@EventListener` 或你自定义的监听器类都不会被触发,订阅逻辑将静默失效。
一个典型的踩坑现象是:启动日志中看不到 “Starting RedisMessageListenerContainer” 这条信息,消息发布后监听器没有任何响应,也没有错误提示,仿佛消息石沉大海。
正确的做法很简单:手工声明一个 Bean,并确保它依赖已配置好的 `RedisConnectionFactory`:
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setTaskExecutor(new ThreadPoolTaskExecutor()); // 避免阻塞主线程
return container;
}
监听器注册要用 addMessageListener(),不是 @EventListener
Spring 的 `@EventListener` 注解仅适用于 Spring 自身的事件体系(`ApplicationEvent`),对 Redis 的发布/订阅消息完全无效。你必须通过 `RedisMessageListenerContainer.addMessageListener()` 方法将监听器实例显式注册到容器中。
一个常见的误区是:编写一个带有 `@Component` 和 `@EventListener` 的类,以为它能接收到 Redis 消息——实际上永远无法生效。
实操建议:
- 监听器必须实现 `MessageListener` 接口(或者继承 `KeyExpirationEventMessageListener` 等子类)
- 使用 `PatternTopic` 订阅通配符频道(例如 `PatternTopic("user:*")`),使用 `ChannelTopic` 订阅精确频道(例如 `ChannelTopic("user.login")`)
- 注册时传入监听器实例和 Topic 列表,示例:`container.addMessageListener(myListener, new ChannelTopic("order.created"))`
消息序列化问题常导致 onMessage() 收到 null 或乱码
默认情况下,`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` 对同一组频道进行收发消息,否则会引发序列化冲突
发布端必须用 RedisTemplate 或 StringRedisTemplate 的 convertAndSend()
切勿使用 `execute()` 加原生 `PUBLISH` 命令绕过序列化逻辑,否则监听器收到的消息体无法正确反序列化——尤其是在容器配置了非默认序列化器的情况下。
正确的发布方式只有两种:
- `redisTemplate.convertAndSend("channel.name", "hello")` —— 前提是 `redisTemplate` 的 value serializer 与监听器的解码方式一致
- `stringRedisTemplate.convertAndSend("channel.name", "hello")` —— 最简洁可靠的方案,强烈推荐用于纯字符串消息
一个容易被忽略的细节:`convertAndSend()` 虽然是异步非阻塞的,但如果连接池耗尽或 Redis 服务宕机,它会抛出 `RedisConnectionFailureException`。因此外层最好捕获异常,不要天真地认为“消息发出去就一定能到达”。
