为什么我的动态IntegrationFlow无法路由到错误通道?

时间:2019-07-15 07:37:00

标签: spring-integration

我正在创建一个Spring Integration应用程序,该应用程序需要动态实例化IntegrationFlow。我的流程如下所示:

kafkaListener -> intermediateChannel -> httpOutboundGateway,其中httpOutboundGateway通过ExpressionEvaluatingRequestHandlerAdvice将错误路由到持久errorChannel来建议。该errorChannel有一个轮询器和一个IntegrationFlow,它将返回错误返回到intermediateChannel中。

当所有流和errorChannel bean由Spring创建并自动自动装配时,该方案就像一个超级按钮。但是,当我尝试以编程方式实例化所有实例时,只有满意的路径有效(kafka -> intermediateChannel -> httpOutboundGateway),但是errorChannel没有收到数据-doSend消息中的日志字符串未打印,并且流程没有重试错误。登录启动时未发生任何错误或警告,只是该建议无效。

我使用了here的一些建议,他们帮助注册了流程并让工作顺利了。但是,在注册不是流本身而是支持bean时,我似乎缺少了一些东西。

我还尝试在调试中检查相应的errorChannel bean是否已连接到applicationContext中,的确如此。可以通过errorChannelName查找,并且可以在IntegrationFlow注册时刻使用。

这是我的代码。

我在@PostConstruct中手动创建一个错误通道,并注册它和流:

   @PostConstruct
   fun topology() {
       rules.forEach { rule ->
           val mainChannelName = rule.topicFrom + "-channel"
           val errorChannelName = rule.topicFrom + "-error-channel"
           val errorChannel = channelProducer.createPollableDatabaseChannel(errorChannelName)

           val topicToChannelFlow = integrationFlowProducer.fromTopicToChannel(rule.topicFrom, mainChannelName)
           val channelToEndpointFlow = integrationFlowProducer.
               fromChannelToEndpoint(mainChannelName, rule.endpointDetails, errorChannelName)
           val errorChannelToMainFlow = integrationFlowProducer.fromErrorChannelToMain(errorChannelName, mainChannelName)


           integrationFlowContext.registration(topicToChannelFlow).register()
           integrationFlowContext.registration(channelToEndpointFlow).addBean(errorChannel).register()
           integrationFlowContext.registration(errorChannelToMainFlow).register()
       }
   }

这是channelProducer.createPollableDatabaseChannel(errorChannelName)方法的实现:

    fun createPollableDatabaseChannel(channelName: String): PollableChannel {
        val queueChannel = object: QueueChannel(MessageGroupQueue(jdbcStore, channelName)) {
            override fun doSend(message: Message<*>, timeout: Long): Boolean {
                logger.info("sending message to error channel: $message")
                return super.doSend(message, timeout)
            }

            override fun doReceive(timeout: Long): Message<*> {
                val received = super.doReceive(timeout)
                logger.info("received message: $received")
                return received
            }
        }
        return queueChannel
    }

这是上面在integrationFlowProducer中调用的方法:

    fun fromTopicToChannel(topicFrom: String, receiverChannel: String): IntegrationFlow {
        return IntegrationFlows
            .from(
                Kafka.messageDrivenChannelAdapter(
                    consumerFactory,
                    KafkaMessageDrivenChannelAdapter.ListenerMode.record, topicFrom
                )
                    .configureListenerContainer { c ->
                        c.ackMode(ContainerProperties.AckMode.RECORD)
                    }
            )
            .log()
            .channel(receiverChannel)
            .get()
    }

    fun fromChannelToEndpoint(
        channelFrom: String,
        endpointDetails: EndpointDetails,
        errorChannelName: String
    ): IntegrationFlow {

        if (endpointDetails is RestEndpointDetails) {
            return createRestIntegrationFlow(channelFrom, endpointDetails, errorChannelName)
        }

        throw UnsupportedOperationException("only rest endpoint supported")
    }

    fun fromErrorChannelToMain(errorChannelName: String, mainChannelName: String): IntegrationFlow {

        return IntegrationFlows.from(errorChannelName)
            .wireTap {f -> f.handle {t -> logger.info("Message read from error channel: " + t.payload.toString())}}
            .transform<ErrorMessage, String> { extractPayloadFromErrorMessage(it) }
            .channel(mainChannelName)
            .get()
    }

    private fun createRestIntegrationFlow(
        channelFrom: String,
        endpointDetails: RestEndpointDetails,
        errorChannelName: String
    ): IntegrationFlow {
        return IntegrationFlows.from(channelFrom)
            .wireTap { f -> f.handle { t -> logger.info("Message read from main channel: " + t.payload.toString()) } }
            .handle<HttpRequestExecutingMessageHandler>(
                Http.outboundGateway(
                    endpointDetails.url,
                    sslRestTemplate
                )
                    .httpMethod(HttpMethod.POST)
                    .headerMapper(kafkaToHttpHeaderMapper)
                    .expectedResponseType(String::class.java)


            ) { c -> c.advice(failureAdvice(errorChannelName)) }
            .nullChannel()
    }

    private fun extractPayloadFromErrorMessage(errorMessage: ErrorMessage) =
        (errorMessage.payload as EvaluatingException).failedMessage!!.payload as String

    private fun failureAdvice(errorChannelName: String): Advice {
        val advice = ExpressionEvaluatingRequestHandlerAdvice()
        advice.setFailureChannelName(errorChannelName)
        return advice
    }

按以下方式创建轮询器,这是一个自动初始化的bean:

    @Bean(name = [PollerMetadata.DEFAULT_POLLER])
    fun poller(deliveryTransactionInterceptor: TransactionInterceptor,
               deliveryThreadPoolTaskExecutor: TaskExecutor): PollerMetadata {
        return Pollers.fixedDelay(30, TimeUnit.SECONDS)
            .advice(deliveryTransactionInterceptor)
            .taskExecutor(deliveryThreadPoolTaskExecutor)
            .get()
    }

还有其他一些持久性设置,但是对于工作(自动初始化)的代码和当前代码,它们保持不变。如果需要它们,我也可以将其粘贴在这里。

Spring Integration的版本是5.1.4.RELEASE。

非常感谢您对此问题的帮助。

1 个答案:

答案 0 :(得分:1)

由于您可以即时进行所有操作,因此看起来每个动态流都有一个ExpressionEvaluatingRequestHandlerAdvice实例。我认为也必须将其注册为bean。流注册上的addBean()是必经之路。我知道您需要重新编写一些代码,但实际上也必须像bean一样。