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

Spring Integration测试报错:ReactiveStreamsConsumer的subscriber不能为空

问题:测试因缺失'subscriber'字段失败

报错信息

IllegalArgumentException: 'subscriber' must not be null in the "...endpoint"

测试代码

@Test
public void test() throws InterruptedException {
    ArgumentCaptor<Message<?>> captor = messageArgumentCaptor();
    CountDownLatch receiveLatch = new CountDownLatch(1);
    MessageHandler mockMessageHandler = mockMessageHandler(captor).handleNext(m -> receiveLatch.countDown());
    this.mockIntegrationContext
            .substituteMessageHandlerFor(
                    "test2.org.springframework.integration.config.ConsumerEndpointFactoryBean#1",
                    mockMessageHandler);
    this.integrationFlowWithReactiveConsumerHandler.getInputChannel().send(new GenericMessage<>("test2"));
    assertThat(receiveLatch.await(10, TimeUnit.SECONDS)).isTrue();
    verify(mockMessageHandler).handleMessage(any());
    assertThat(captor.getValue().getPayload())
            .isEqualTo("reactive-message-text");
}

报错触发点

报错发生在MockIntegrationContext.java的substituteMessageHandlerFor方法中,当处理ReactiveStreamsConsumer类型端点时,代码对subscriber字段做了非空校验:

public void substituteMessageHandlerFor(String consumerEndpointId, // NOSONAR - complexity
        MessageHandler mockMessageHandler, boolean autoStartup) {

    Object endpoint = this.beanFactory.getBean(consumerEndpointId, IntegrationConsumer.class);
    if (autoStartup && endpoint instanceof Lifecycle) {
        ((Lifecycle) endpoint).stop();
    }
    DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(endpoint);
    Object targetMessageHandler = directFieldAccessor.getPropertyValue(HANDLER);
    Assert.notNull(targetMessageHandler, () -> "'handler' must not be null in the: " + endpoint);
    if (endpoint instanceof ReactiveStreamsConsumer) {
        Object targetSubscriber = directFieldAccessor.getPropertyValue("subscriber");
        Assert.notNull(targetSubscriber, () -> "'subscriber' must not be null in the: " + endpoint);

问题根源

处理器端点Bean为...:reactive-outbound-channel-adapter,通过ReactiveStreamsConsumer构造函数实例化时,subscriber字段默认被设为null,只有在端点启动(调用start()方法)时才会完成初始化:

/**
 * Instantiate an endpoint based on the provided {@link MessageChannel} and {@link ReactiveMessageHandler}.
 * @param inputChannel the channel to consume in reactive manner.
 * @param reactiveMessageHandler the {@link ReactiveMessageHandler} to process messages.
 * @since 5.3
 */
public ReactiveStreamsConsumer(MessageChannel inputChannel, ReactiveMessageHandler reactiveMessageHandler) {
    Assert.notNull(inputChannel, "'inputChannel' must not be null");
    this.inputChannel = inputChannel;
    this.handler = new ReactiveMessageHandlerAdapter(reactiveMessageHandler);
    this.reactiveMessageHandler = reactiveMessageHandler;
    this.publisher = IntegrationReactiveUtils.messageChannelToFlux(inputChannel);
    this.subscriber = null;
    this.lifecycleDelegate =
            reactiveMessageHandler instanceof Lifecycle ? (Lifecycle) reactiveMessageHandler : null;
}

解决方案

方案1:确保端点先启动再替换处理器

修改测试代码,先启动目标端点让subscriber完成初始化,再调用替换方法:

@Test
public void test() throws InterruptedException {
    ArgumentCaptor<Message<?>> captor = messageArgumentCaptor();
    CountDownLatch receiveLatch = new CountDownLatch(1);
    MessageHandler mockMessageHandler = mockMessageHandler(captor).handleNext(m -> receiveLatch.countDown());
    
    // 先获取端点并启动
    IntegrationConsumer endpoint = this.beanFactory.getBean("test2.org.springframework.integration.config.ConsumerEndpointFactoryBean#1", IntegrationConsumer.class);
    if (endpoint instanceof Lifecycle) {
        ((Lifecycle) endpoint).start();
    }
    
    this.mockIntegrationContext
            .substituteMessageHandlerFor(
                    "test2.org.springframework.integration.config.ConsumerEndpointFactoryBean#1",
                    mockMessageHandler);
    this.integrationFlowWithReactiveConsumerHandler.getInputChannel().send(new GenericMessage<>("test2"));
    assertThat(receiveLatch.await(10, TimeUnit.SECONDS)).isTrue();
    verify(mockMessageHandler).handleMessage(any());
    assertThat(captor.getValue().getPayload())
            .isEqualTo("reactive-message-text");
}

方案2:直接替换ReactiveMessageHandler而非MessageHandler

绕过substituteMessageHandlerFor对subscriber的校验,通过DirectFieldAccessor直接替换ReactiveStreamsConsumer中的核心处理器:

@Test
public void test() throws InterruptedException {
    ArgumentCaptor<Message<?>> captor = messageArgumentCaptor();
    CountDownLatch receiveLatch = new CountDownLatch(1);
    
    // 创建自定义的ReactiveMessageHandler模拟实现
    ReactiveMessageHandler mockReactiveHandler = message -> {
        receiveLatch.countDown();
        captor.setValue(message);
        return Mono.empty();
    };
    
    // 获取端点并替换reactiveMessageHandler
    ReactiveStreamsConsumer endpoint = this.beanFactory.getBean("test2.org.springframework.integration.config.ConsumerEndpointFactoryBean#1", ReactiveStreamsConsumer.class);
    DirectFieldAccessor accessor = new DirectFieldAccessor(endpoint);
    accessor.setPropertyValue("reactiveMessageHandler", mockReactiveHandler);
    // 同时替换适配器中的关联处理器
    ReactiveMessageHandlerAdapter adapter = (ReactiveMessageHandlerAdapter) accessor.getPropertyValue("handler");
    new DirectFieldAccessor(adapter).setPropertyValue("handler", mockReactiveHandler);
    
    this.integrationFlowWithReactiveConsumerHandler.getInputChannel().send(new GenericMessage<>("test2"));
    assertThat(receiveLatch.await(10, TimeUnit.SECONDS)).isTrue();
    assertThat(captor.getValue().getPayload())
            .isEqualTo("reactive-message-text");
}

方案3:升级Spring Integration版本

如果使用的是较旧版本,该问题可能是已知bug,已在5.5.x及以上版本中修复,升级到最新稳定版即可解决。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 09:05:31