Commit 2cef84bdc836de9041b5c68dcba0f0e59f666277
1 parent
15245310
* 【修复】自定义 DefaultStreamMessageListenerContainerX 实现,解决 Redisson Stream 读取不到数据返回 `null` 导致 NPE 问题
Showing
3 changed files
with
67 additions
and
2 deletions
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java
| @@ -18,6 +18,7 @@ import org.springframework.data.redis.core.RedisTemplate; | @@ -18,6 +18,7 @@ import org.springframework.data.redis.core.RedisTemplate; | ||
| 18 | import org.springframework.data.redis.core.StringRedisTemplate; | 18 | import org.springframework.data.redis.core.StringRedisTemplate; |
| 19 | import org.springframework.data.redis.listener.ChannelTopic; | 19 | import org.springframework.data.redis.listener.ChannelTopic; |
| 20 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; | 20 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; |
| 21 | +import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX; | ||
| 21 | import org.springframework.data.redis.stream.StreamMessageListenerContainer; | 22 | import org.springframework.data.redis.stream.StreamMessageListenerContainer; |
| 22 | 23 | ||
| 23 | import java.util.List; | 24 | import java.util.List; |
| @@ -81,11 +82,12 @@ public class YudaoMQAutoConfiguration { | @@ -81,11 +82,12 @@ public class YudaoMQAutoConfiguration { | ||
| 81 | .build(); | 82 | .build(); |
| 82 | // 创建 container 对象 | 83 | // 创建 container 对象 |
| 83 | StreamMessageListenerContainer<String, ObjectRecord<String, String>> container = | 84 | StreamMessageListenerContainer<String, ObjectRecord<String, String>> container = |
| 84 | - StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions); | 85 | +// StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions); |
| 86 | + DefaultStreamMessageListenerContainerX.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions); | ||
| 85 | 87 | ||
| 86 | // 第二步,注册监听器,消费对应的 Stream 主题 | 88 | // 第二步,注册监听器,消费对应的 Stream 主题 |
| 87 | String consumerName = buildConsumerName(); | 89 | String consumerName = buildConsumerName(); |
| 88 | - listeners.forEach(listener -> { | 90 | + listeners.parallelStream().forEach(listener -> { |
| 89 | // 创建 listener 对应的消费者分组 | 91 | // 创建 listener 对应的消费者分组 |
| 90 | try { | 92 | try { |
| 91 | redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup()); | 93 | redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup()); |
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/org/springframework/data/redis/stream/DefaultStreamMessageListenerContainerX.java
0 → 100644
| 1 | +package org.springframework.data.redis.stream; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.ReflectUtil; | ||
| 4 | +import org.springframework.data.redis.connection.RedisConnectionFactory; | ||
| 5 | +import org.springframework.data.redis.connection.stream.ByteRecord; | ||
| 6 | +import org.springframework.data.redis.connection.stream.ReadOffset; | ||
| 7 | +import org.springframework.data.redis.connection.stream.Record; | ||
| 8 | +import org.springframework.util.Assert; | ||
| 9 | + | ||
| 10 | +import java.util.Collections; | ||
| 11 | +import java.util.List; | ||
| 12 | +import java.util.function.Function; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * 拓展 DefaultStreamMessageListenerContainer 实现,解决 Spring Data Redis + Redisson 结合使用时,Redisson 在 Stream 获得不到数据时,返回 null 而不是空 List,导致 NPE 异常。 | ||
| 16 | + * 对应 issue:https://github.com/spring-projects/spring-data-redis/issues/2147 和 https://github.com/redisson/redisson/issues/4006 | ||
| 17 | + * 目前看下来 Spring Data Redis 不肯加 null 判断,Redisson 暂时也没改返回 null 到空 List 的打算,所以暂时只能自己改,哽咽! | ||
| 18 | + * | ||
| 19 | + * @author 芋道源码 | ||
| 20 | + */ | ||
| 21 | +public class DefaultStreamMessageListenerContainerX<K, V extends Record<K, ?>> extends DefaultStreamMessageListenerContainer<K, V> { | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 参考 {@link StreamMessageListenerContainer#create(RedisConnectionFactory, StreamMessageListenerContainerOptions)} 的实现 | ||
| 25 | + */ | ||
| 26 | + public static <K, V extends Record<K, ?>> StreamMessageListenerContainer<K, V> create(RedisConnectionFactory connectionFactory, StreamMessageListenerContainer.StreamMessageListenerContainerOptions<K, V> options) { | ||
| 27 | + Assert.notNull(connectionFactory, "RedisConnectionFactory must not be null!"); | ||
| 28 | + Assert.notNull(options, "StreamMessageListenerContainerOptions must not be null!"); | ||
| 29 | + return new DefaultStreamMessageListenerContainerX<>(connectionFactory, options); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public DefaultStreamMessageListenerContainerX(RedisConnectionFactory connectionFactory, StreamMessageListenerContainerOptions<K, V> containerOptions) { | ||
| 33 | + super(connectionFactory, containerOptions); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 参考 {@link DefaultStreamMessageListenerContainer#register(StreamReadRequest, StreamListener)} 的实现 | ||
| 38 | + */ | ||
| 39 | + @Override | ||
| 40 | + public Subscription register(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) { | ||
| 41 | + return this.doRegisterX(getReadTaskX(streamRequest, listener)); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @SuppressWarnings("unchecked") | ||
| 45 | + private StreamPollTask<K, V> getReadTaskX(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) { | ||
| 46 | + StreamPollTask<K, V> task = ReflectUtil.invoke(this, "getReadTask", streamRequest, listener); | ||
| 47 | + // 修改 readFunction 方法 | ||
| 48 | + Function<ReadOffset, List<ByteRecord>> readFunction = (Function<ReadOffset, List<ByteRecord>>) ReflectUtil.getFieldValue(task, "readFunction"); | ||
| 49 | + ReflectUtil.setFieldValue(task, "readFunction", (Function<ReadOffset, List<ByteRecord>>) readOffset -> { | ||
| 50 | + List<ByteRecord> records = readFunction.apply(readOffset); | ||
| 51 | + //【重点】保证 records 不是空,避免 NPE 的问题!!! | ||
| 52 | + return records != null ? records : Collections.emptyList(); | ||
| 53 | + }); | ||
| 54 | + return task; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + private Subscription doRegisterX(Task task) { | ||
| 58 | + return ReflectUtil.invoke(this, "doRegister", task); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | +} | ||
| 62 | + |
更新日志.md
| @@ -37,6 +37,7 @@ TODO | @@ -37,6 +37,7 @@ TODO | ||
| 37 | * 【修复】Knife4j 接口文档 404 的问题,原因是 `spring.mvc.static-path-pattern` 配置项,影响了基础路径 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/f966fae0606178ec5ffa47dd487f7984867da557) | 37 | * 【修复】Knife4j 接口文档 404 的问题,原因是 `spring.mvc.static-path-pattern` 配置项,影响了基础路径 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/f966fae0606178ec5ffa47dd487f7984867da557) |
| 38 | * 【修复】修复文件访问地址错误 [#68](https://github.com/YunaiV/ruoyi-vue-pro/pull/68) | 38 | * 【修复】修复文件访问地址错误 [#68](https://github.com/YunaiV/ruoyi-vue-pro/pull/68) |
| 39 | * 【修复】工作流程发起以及审批异常,由 `@NotEmpty` 校验、和 Long 类型异常导致 [#73](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/73) | 39 | * 【修复】工作流程发起以及审批异常,由 `@NotEmpty` 校验、和 Long 类型异常导致 [#73](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/73) |
| 40 | +* 【修复】自定义 DefaultStreamMessageListenerContainerX 实现,解决 Redisson Stream 读取不到数据返回 `null` 导致 NPE 问题 [commit]() | ||
| 40 | 41 | ||
| 41 | ### 🔨 Dependency Upgrades | 42 | ### 🔨 Dependency Upgrades |
| 42 | 43 |