配置消息通道
要创建消息通道实例,您可以使用 XML 中的<channel/>
元素或 Java 配置中的DirectChannel
实例,如下所示
-
Java
-
XML
@Bean
public MessageChannel exampleChannel() {
return new DirectChannel();
}
<int:channel id="exampleChannel"/>
当您在没有任何子元素的情况下使用<channel/>
元素时,它会创建一个DirectChannel
实例(一个SubscribableChannel
)。
要创建发布-订阅通道,请使用<publish-subscribe-channel/>
元素(Java 中的PublishSubscribeChannel
),如下所示
-
Java
-
XML
@Bean
public MessageChannel exampleChannel() {
return new PublishSubscribeChannel();
}
<int:publish-subscribe-channel id="exampleChannel"/>
或者,您可以提供各种<queue/>
子元素来创建任何可轮询的通道类型(如消息通道实现中所述)。以下部分显示了每种通道类型的示例。
DirectChannel
配置
如前所述,DirectChannel
是默认类型。以下清单显示了如何定义一个
-
Java
-
XML
@Bean
public MessageChannel directChannel() {
return new DirectChannel();
}
<int:channel id="directChannel"/>
默认通道具有循环负载均衡器,并且启用了故障转移(有关更多详细信息,请参阅DirectChannel
)。要禁用其中一个或两个,请添加一个<dispatcher/>
子元素(DirectChannel
的LoadBalancingStrategy
构造函数)并按如下方式配置属性
-
Java
-
XML
@Bean
public MessageChannel failFastChannel() {
DirectChannel channel = new DirectChannel();
channel.setFailover(false);
return channel;
}
@Bean
public MessageChannel failFastChannel() {
return new DirectChannel(null);
}
<int:channel id="failFastChannel">
<int:dispatcher failover="false"/>
</channel>
<int:channel id="channelWithFixedOrderSequenceFailover">
<int:dispatcher load-balancer="none"/>
</int:channel>
从 6.3 版本开始,所有基于UnicastingDispatcher
的MessageChannel
实现都可以配置Predicate<Exception> failoverStrategy
,而不是简单的failover
选项。此谓词根据当前抛出的异常决定是否故障转移到下一个MessageHandler
。更复杂的错误分析应该使用ErrorMessageExceptionTypeRouter
完成。
数据类型通道配置
有时,使用者只能处理特定类型的有效负载,这迫使您确保输入消息的有效负载类型。首先想到的可能是使用消息过滤器。但是,所有消息过滤器能做的就是过滤掉不符合使用者要求的消息。另一种方法是使用基于内容的路由器,并将有效负载数据类型不符合要求的消息路由到特定的转换器,以强制执行转换并转换为所需的数据类型。这可以工作,但完成相同操作的更简单方法是应用数据类型通道模式。您可以为每种特定的有效负载数据类型使用单独的数据类型通道。
要创建一个仅接受包含特定有效负载类型消息的数据类型通道,请在通道元素的datatype
属性中提供数据类型的完全限定类名,如下例所示
-
Java
-
XML
@Bean
public MessageChannel numberChannel() {
DirectChannel channel = new DirectChannel();
channel.setDatatypes(Number.class);
return channel;
}
<int:channel id="numberChannel" datatype="java.lang.Number"/>
请注意,类型检查适用于可分配给通道数据类型的任何类型。换句话说,前面示例中的numberChannel
将接受有效负载为java.lang.Integer
或java.lang.Double
的消息。可以以逗号分隔列表的形式提供多个类型,如下例所示
-
Java
-
XML
@Bean
public MessageChannel numberChannel() {
DirectChannel channel = new DirectChannel();
channel.setDatatypes(String.class, Number.class);
return channel;
}
<int:channel id="stringOrNumberChannel" datatype="java.lang.String,java.lang.Number"/>
因此,前面示例中的'numberChannel'仅接受数据类型为java.lang.Number
的消息。但是,如果消息的有效负载不是所需的类型会发生什么情况?这取决于您是否已定义名为integrationConversionService
的 bean,该 bean 是 Spring 的转换服务的实例。如果没有,则会立即抛出Exception
。但是,如果您已定义integrationConversionService
bean,则会尝试将其用于将消息的有效负载转换为可接受的类型。
您甚至可以注册自定义转换器。例如,假设您向我们上面配置的'numberChannel'发送一条有效负载为String
的消息。您可以如下处理消息
MessageChannel inChannel = context.getBean("numberChannel", MessageChannel.class);
inChannel.send(new GenericMessage<String>("5"));
通常,这将是一个完全合法的操作。但是,由于我们使用的是数据类型通道,因此此类操作的结果将生成类似于以下内容的异常
Exception in thread "main" org.springframework.integration.MessageDeliveryException:
Channel 'numberChannel'
expected one of the following datataypes [class java.lang.Number],
but received [class java.lang.String]
…
发生异常是因为我们要求有效负载类型为Number
,但我们发送了一个String
。因此,我们需要一些东西将String
转换为Number
。为此,我们可以实现类似于以下示例的转换器
public static class StringToIntegerConverter implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.parseInt(source);
}
}
然后,我们可以将其作为转换器注册到集成转换服务中,如下例所示
-
Java
-
XML
@Bean
@IntegrationConverter
public StringToIntegerConverter strToInt {
return new StringToIntegerConverter();
}
<int:converter ref="strToInt"/>
<bean id="strToInt" class="org.springframework.integration.util.Demo.StringToIntegerConverter"/>
或者在StringToIntegerConverter
类上,当它使用@Component
注释进行自动扫描时。
解析'converter'元素时,如果尚未定义,它将创建integrationConversionService
bean。有了该转换器,send
操作现在将成功,因为数据类型通道使用该转换器将String
有效负载转换为Integer
。
有关有效负载类型转换的更多信息,请参阅有效负载类型转换。
从 4.0 版本开始,integrationConversionService
由DefaultDatatypeChannelMessageConverter
调用,后者在应用程序上下文中查找转换服务。要使用不同的转换技术,您可以在通道上指定message-converter
属性。这必须是MessageConverter
实现的引用。仅使用fromMessage
方法。它为转换器提供对消息头的访问权限(如果转换可能需要来自消息头的信息,例如content-type
)。该方法只能返回转换后的有效负载或完整的Message
对象。如果是后者,则转换器必须小心地从传入消息中复制所有头。
或者,您可以声明类型为MessageConverter
且 ID 为datatypeChannelMessageConverter
的<bean/>
,并且该转换器将由所有具有datatype
的通道使用。
QueueChannel
配置
要创建QueueChannel
,请使用<queue/>
子元素。您可以按如下方式指定通道的容量
-
Java
-
XML
@Bean
public PollableChannel queueChannel() {
return new QueueChannel(25);
}
<int:channel id="queueChannel">
<queue capacity="25"/>
</int:channel>
如果您未为此<queue/> 子元素上的'capacity'属性提供值,则生成的队列将是无界的。为避免内存不足等问题,我们强烈建议您为有界队列设置显式值。 |
持久化QueueChannel
配置
由于QueueChannel
提供了缓冲消息的功能,但默认情况下仅在内存中进行,因此它也带来了在系统发生故障时可能丢失消息的可能性。为了降低这种风险,QueueChannel
可以由MessageGroupStore
策略接口的持久化实现支持。有关MessageGroupStore
和MessageStore
的更多详细信息,请参阅消息存储。
当使用message-store 属性时,不允许使用capacity 属性。 |
当QueueChannel
接收Message
时,它会将消息添加到消息存储中。当从QueueChannel
轮询Message
时,它会从消息存储中删除。
默认情况下,QueueChannel
将其消息存储在内存队列中,这可能导致前面提到的消息丢失情况。但是,Spring Integration 提供了持久化存储,例如JdbcChannelMessageStore
。
您可以通过添加message-store
属性为任何QueueChannel
配置消息存储,如下例所示
<int:channel id="dbBackedChannel">
<int:queue message-store="channelStore"/>
</int:channel>
<bean id="channelStore" class="o.s.i.jdbc.store.JdbcChannelMessageStore">
<property name="dataSource" ref="dataSource"/>
<property name="channelMessageStoreQueryProvider" ref="queryProvider"/>
</bean>
(请参阅下面的 Java/Kotlin 配置选项示例。)
Spring Integration JDBC 模块还为许多流行的数据库提供了模式数据定义语言 (DDL)。这些模式位于该模块(spring-integration-jdbc
)的 org.springframework.integration.jdbc.store.channel 包中。
一个重要的特性是,对于任何事务性持久化存储(例如JdbcChannelMessageStore ),只要轮询器配置了事务,只有在事务成功完成时,从存储中删除的消息才能永久删除。否则,事务将回滚,并且不会丢失Message 。 |
随着越来越多的与“NoSQL”数据存储相关的 Spring 项目开始为这些存储提供底层支持,还有许多其他消息存储实现可用。如果找不到满足您特定需求的实现,您还可以提供您自己的MessageGroupStore
接口实现。
从 4.0 版本开始,我们建议尽可能将QueueChannel
实例配置为使用ChannelMessageStore
。与通用消息存储相比,这些通常针对此用途进行了优化。如果ChannelMessageStore
是ChannelPriorityMessageStore
,则消息将按照优先级顺序以 FIFO 方式接收。优先级的概念由消息存储实现确定。例如,以下示例显示了MongoDB 通道消息存储的 Java 配置
-
Java
-
Java DSL
-
Kotlin DSL
@Bean
public BasicMessageGroupStore mongoDbChannelMessageStore(MongoDbFactory mongoDbFactory) {
MongoDbChannelMessageStore store = new MongoDbChannelMessageStore(mongoDbFactory);
store.setPriorityEnabled(true);
return store;
}
@Bean
public PollableChannel priorityQueue(BasicMessageGroupStore mongoDbChannelMessageStore) {
return new PriorityChannel(new MessageGroupQueue(mongoDbChannelMessageStore, "priorityQueue"));
}
@Bean
public IntegrationFlow priorityFlow(PriorityCapableChannelMessageStore mongoDbChannelMessageStore) {
return IntegrationFlow.from((Channels c) ->
c.priority("priorityChannel", mongoDbChannelMessageStore, "priorityGroup"))
....
.get();
}
@Bean
fun priorityFlow(mongoDbChannelMessageStore: PriorityCapableChannelMessageStore) =
integrationFlow {
channel { priority("priorityChannel", mongoDbChannelMessageStore, "priorityGroup") }
}
注意MessageGroupQueue 类。这是一个BlockingQueue 实现,用于使用MessageGroupStore 操作。 |
<int:queue>
子元素或其特定构造函数的ref
属性提供了自定义QueueChannel
环境的另一个选项。此属性提供对任何java.util.Queue
实现的引用。例如,可以如下配置 Hazelcast 分布式IQueue
@Bean
public HazelcastInstance hazelcastInstance() {
return Hazelcast.newHazelcastInstance(new Config()
.setProperty("hazelcast.logging.type", "log4j"));
}
@Bean
public PollableChannel distributedQueue() {
return new QueueChannel(hazelcastInstance()
.getQueue("springIntegrationQueue"));
}
PublishSubscribeChannel
配置
要创建PublishSubscribeChannel
,请使用<publish-subscribe-channel/>元素。使用此元素时,您还可以指定用于发布消息的task-executor
(如果未指定,则在发送者的线程中发布),如下所示
-
Java
-
XML
@Bean
public MessageChannel pubsubChannel() {
return new PublishSubscribeChannel(someExecutor());
}
<int:publish-subscribe-channel id="pubsubChannel" task-executor="someExecutor"/>
如果您在PublishSubscribeChannel
的下游提供了一个重排序器或聚合器,则可以在通道上将'apply-sequence'属性设置为true
。这样做表示通道应在传递消息之前设置sequence-size
和sequence-number
消息头以及相关 ID。例如,如果有五个订阅者,则sequence-size
将设置为5
,并且消息将具有范围从1
到5
的sequence-number
头值。
除了Executor
之外,您还可以配置一个ErrorHandler
。默认情况下,PublishSubscribeChannel
使用MessagePublishingErrorHandler
实现将错误发送到errorChannel
头中的MessageChannel
或全局errorChannel
实例中。如果未配置Executor
,则会忽略ErrorHandler
,并且异常会直接抛到调用者的线程中。
如果在 PublishSubscribeChannel
后面提供 Resequencer
或 Aggregator
,则可以在通道上将“apply-sequence”属性设置为 true
。这样做表示通道应在传递消息之前设置 sequence-size 和 sequence-number 消息头以及相关 ID。例如,如果有五个订阅者,则 sequence-size 将设置为 5
,并且消息的 sequence-number 头值范围将从 1
到 5
。
以下示例显示如何将 apply-sequence
头设置为 true
-
Java
-
XML
@Bean
public MessageChannel pubsubChannel() {
PublishSubscribeChannel channel = new PublishSubscribeChannel();
channel.setApplySequence(true);
return channel;
}
<int:publish-subscribe-channel id="pubsubChannel" apply-sequence="true"/>
默认情况下,apply-sequence 值为 false ,以便发布-订阅通道可以将完全相同的消息实例发送到多个出站通道。由于 Spring Integration 强制执行有效负载和标头引用的不变性,因此当将标志设置为 true 时,通道会创建具有相同有效负载引用但标头值不同的新 Message 实例。 |
从版本 5.4.3 开始,PublishSubscribeChannel
还可以使用其 BroadcastingDispatcher
的 requireSubscribers
选项进行配置,以指示此通道在没有订阅者时不会静默忽略消息。当没有订阅者并且此选项设置为 true
时,将抛出带有“Dispatcher has no subscribers”消息的 MessageDispatchingException
。
ExecutorChannel
要创建 ExecutorChannel
,请添加带有 task-executor
属性的 <dispatcher>
子元素。属性的值可以引用上下文中的任何 TaskExecutor
。例如,这样做可以配置线程池以将消息分派给已订阅的处理程序。如前所述,这样做会破坏发送方和接收方之间的单线程执行上下文,因此处理程序的调用不会共享任何活动的交易上下文(即,处理程序可能会抛出 Exception
,但 send
调用已经成功返回)。以下示例显示了如何使用 dispatcher
元素并在 task-executor
属性中指定执行程序
-
Java
-
XML
@Bean
public MessageChannel executorChannel() {
return new ExecutorChannel(someExecutor());
}
<int:channel id="executorChannel">
<int:dispatcher task-executor="someExecutor"/>
</int:channel>
|
PriorityChannel
配置
要创建 PriorityChannel
,请使用 <priority-queue/>
子元素,如下例所示
-
Java
-
XML
@Bean
public PollableChannel priorityChannel() {
return new PriorityChannel(20);
}
<int:channel id="priorityChannel">
<int:priority-queue capacity="20"/>
</int:channel>
默认情况下,通道会查询消息的 priority
标头。但是,您可以改为提供自定义的 Comparator
引用。另外,请注意 PriorityChannel
(与其他类型一样)支持 datatype
属性。与 QueueChannel
一样,它也支持 capacity
属性。以下示例演示了所有这些
-
Java
-
XML
@Bean
public PollableChannel priorityChannel() {
PriorityChannel channel = new PriorityChannel(20, widgetComparator());
channel.setDatatypes(example.Widget.class);
return channel;
}
<int:channel id="priorityChannel" datatype="example.Widget">
<int:priority-queue comparator="widgetComparator"
capacity="10"/>
</int:channel>
从 4.0 版本开始,priority-channel
子元素支持 message-store
选项(在这种情况下不允许使用 comparator
和 capacity
)。消息存储必须是 PriorityCapableChannelMessageStore
。目前为 Redis
、JDBC
和 MongoDB
提供了 PriorityCapableChannelMessageStore
的实现。有关更多信息,请参阅QueueChannel
配置和消息存储。您可以在备份消息通道中找到示例配置。
RendezvousChannel
配置
当队列子元素为 <rendezvous-queue>
时,将创建 RendezvousChannel
。它不提供任何超出前面所述的附加配置选项,并且其队列不接受任何容量值,因为它是一个零容量的直接传递队列。以下示例显示了如何声明 RendezvousChannel
-
Java
-
XML
@Bean
public PollableChannel rendezvousChannel() {
return new RendezvousChannel();
}
<int:channel id="rendezvousChannel"/>
<int:rendezvous-queue/>
</int:channel>
通道拦截器配置
消息通道也可以具有拦截器,如通道拦截器中所述。<interceptors/>
子元素可以添加到 <channel/>
(或更具体的元素类型)中。您可以提供 ref
属性来引用实现 ChannelInterceptor
接口的任何 Spring 托管对象,如下例所示
<int:channel id="exampleChannel">
<int:interceptors>
<ref bean="trafficMonitoringInterceptor"/>
</int:interceptors>
</int:channel>
通常,我们建议在单独的位置定义拦截器实现,因为它们通常提供可以在多个通道之间重用的通用行为。
全局通道拦截器配置
通道拦截器提供了一种简洁明了的方式来为每个通道应用横切行为。如果应在多个通道上应用相同的行为,则为每个通道配置相同的拦截器集将不是最有效的方法。为了避免重复配置,同时还使拦截器能够应用于多个通道,Spring Integration 提供了全局拦截器。请考虑以下一对示例
<int:channel-interceptor pattern="input*, thing2*, thing1, !cat*" order="3">
<bean class="thing1.thing2SampleInterceptor"/>
</int:channel-interceptor>
<int:channel-interceptor ref="myInterceptor" pattern="input*, thing2*, thing1, !cat*" order="3"/>
<bean id="myInterceptor" class="thing1.thing2SampleInterceptor"/>
每个 <channel-interceptor/>
元素允许您定义一个全局拦截器,该拦截器应用于与 pattern
属性定义的任何模式匹配的所有通道。在上述情况下,全局拦截器应用于“thing1”通道和以“thing2”或“input”开头的所有其他通道,但不应用于以“thing3”开头的通道(从 5.0 版本开始)。
在模式中添加此语法会导致一个可能(尽管可能不太可能)的问题。如果您有一个名为 !thing1 的 bean,并且在通道拦截器的 pattern 模式中包含了 !thing1 模式,它将不再匹配。该模式现在匹配所有未命名为 thing1 的 bean。在这种情况下,您可以使用 \ 转义模式中的 ! 。模式 \!thing1 匹配名为 !thing1 的 bean。 |
order 属性允许您管理在给定通道上有多个拦截器时注入此拦截器的位置。例如,通道“inputChannel”可以本地配置单个拦截器(请参见下文),如下例所示
<int:channel id="inputChannel">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>
一个合理的问题是“全局拦截器相对于本地或通过其他全局拦截器定义配置的其他拦截器是如何注入的?”当前的实现提供了一种简单的机制来定义拦截器执行的顺序。order
属性中的正数确保在任何现有拦截器之后注入拦截器,而负数确保在现有拦截器之前注入拦截器。这意味着,在前面的示例中,全局拦截器是在(因为其 order
大于 0
)本地配置的“wire-tap”拦截器之后注入的。如果另一个全局拦截器具有匹配的 pattern
,则其顺序将通过比较两个拦截器的 order
属性的值来确定。要在现有拦截器之前注入全局拦截器,请为 order
属性使用负值。
请注意,order 和 pattern 属性都是可选的。order 的默认值为 0,pattern 的默认值为 '*'(匹配所有通道)。 |
线路截取
如前所述,Spring Integration 提供了一个简单的线路截取拦截器。您可以在 <interceptors/>
元素中的任何通道上配置线路截取。这样做对于调试特别有用,并且可以与 Spring Integration 的日志记录通道适配器结合使用,如下所示
<int:channel id="in">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>
<int:logging-channel-adapter id="logger" level="DEBUG"/>
“logging-channel-adapter”还接受“expression”属性,以便您可以针对“payload”和“headers”变量评估 SpEL 表达式。或者,要记录完整的消息 toString() 结果,请为“log-full-message”属性提供 true 值。默认情况下,它是 false ,因此仅记录有效负载。将其设置为 true 可以启用除有效负载之外的所有标头的日志记录。“expression”选项提供了最大的灵活性(例如,expression="payload.user.name" )。 |
关于线路截取和其他类似组件(消息发布配置)的一个常见误解是,它们本质上是异步的。默认情况下,线路截取作为组件不会异步调用。相反,Spring Integration 侧重于配置异步行为的单一统一方法:消息通道。使消息流的某些部分同步或异步的是在该流中配置的消息通道的类型。这是消息通道抽象的主要优势之一。从框架的开始,我们一直强调消息通道作为框架的一等公民的需求和价值。它不仅仅是 EIP 模式的内部隐式实现。它完全作为可配置组件公开给最终用户。因此,线路截取组件仅负责执行以下任务
-
通过点击通道(例如,
channelA
)拦截消息流 -
获取每条消息
-
将消息发送到另一个通道(例如,
channelB
)
它本质上是桥接模式的一种变体,但它封装在通道定义中(因此更易于启用和禁用,而不会中断流)。此外,与桥接不同,它基本上会分叉另一个消息流。该流是同步的还是异步的?答案取决于“channelB”是哪种类型的消息通道。我们有以下选项:直接通道、可轮询通道和执行程序通道。后两者打破了线程边界,使通过此类通道的通信成为异步的,因为从该通道到其已订阅处理程序的消息分派发生在与用于将消息发送到该通道的不同线程上。这就是使您的线路截取流同步或异步的原因。它与框架内的其他组件(例如消息发布者)一致,并通过让您不必事先担心(除了编写线程安全代码之外)某个特定代码段是否应实现为同步或异步,从而增加了一致性和简单性。通过消息通道连接两段代码(例如,组件 A 和组件 B)是使它们的协作同步或异步的原因。您甚至可能希望将来从同步更改为异步,而消息通道可以让您快速完成此操作,而无需触及代码。
关于线缆监听的最后一个要点是,尽管上面提供了关于默认情况下不异步的理由,但您应该记住,通常希望尽快传递消息。因此,使用异步通道选项作为线缆监听器的出站通道非常常见。但是,默认情况下不会强制执行异步行为。如果我们这样做了,许多用例将会中断,包括您可能不希望中断事务边界。也许您将线缆监听模式用于审计目的,并且您确实希望审计消息在原始事务中发送。例如,您可以将线缆监听连接到 JMS 出站通道适配器。这样,您就可以两全其美:1)发送 JMS 消息可以在事务中发生,而 2)它仍然是“发送并忘记”操作,从而防止主消息流出现任何明显的延迟。
从 4.0 版开始,当拦截器(例如 WireTap 类)引用通道时,务必避免循环引用。您需要将此类通道从当前拦截器拦截的通道中排除。这可以通过适当的模式或以编程方式完成。如果您有一个引用 channel 的自定义 ChannelInterceptor ,请考虑实现 VetoCapableInterceptor 。这样,框架会询问拦截器是否可以拦截每个作为候选者的通道,具体取决于提供的模式。您还可以拦截器方法中添加运行时保护,以确保通道不是拦截器引用的通道。WireTap 使用这两种技术。 |
从 4.3 版开始,WireTap
具有其他构造函数,这些构造函数采用 channelName
而不是 MessageChannel
实例。这对于 Java 配置以及使用通道自动创建逻辑时可能很方便。目标 MessageChannel
bean 稍后在第一次与拦截器交互时从提供的 channelName
解析。
通道解析需要一个 BeanFactory ,因此线缆监听实例必须是 Spring 管理的 bean。 |
这种延迟绑定方法还可以简化使用 Java DSL 配置的典型线缆监听模式,如下例所示
@Bean
public PollableChannel myChannel() {
return MessageChannels.queue()
.wireTap("loggingFlow.input")
.get();
}
@Bean
public IntegrationFlow loggingFlow() {
return f -> f.log();
}
条件线缆监听
可以通过使用 selector
或 selector-expression
属性使线缆监听成为条件。selector
引用一个 MessageSelector
bean,该 bean 可以确定在运行时消息是否应该发送到监听通道。类似地,selector-expression
是一个布尔 SpEL 表达式,它执行相同的目的:如果表达式计算结果为 true
,则将消息发送到监听通道。
全局线缆监听配置
可以将全局线缆监听配置为 全局通道拦截器配置 的特例。为此,配置一个顶级 wire-tap
元素。现在,除了正常的 wire-tap
命名空间支持外,还支持 pattern
和 order
属性,并且它们的工作方式与 channel-interceptor
完全相同。以下示例显示了如何配置全局线缆监听
-
Java
-
XML
@Bean
@GlobalChannelInterceptor(patterns = "input*,thing2*,thing1", order = 3)
public WireTap wireTap(MessageChannel wiretapChannel) {
return new WireTap(wiretapChannel);
}
<int:wire-tap pattern="input*, thing2*, thing1" order="3" channel="wiretapChannel"/>
全局线缆监听提供了一种方便的方法来在外部配置单通道线缆监听,而无需修改现有的通道配置。为此,将 pattern 属性设置为目标通道名称。例如,您可以使用此技术配置测试用例以验证通道上的消息。 |