# 配置
# 配置
Spring 集成提供了许多配置选项。你选择哪一种选择取决于你的特殊需求,以及你更喜欢工作的级别。与 Spring 框架一般一样,你可以混合和匹配各种技术以适应手头的问题。例如,你可以为大多数配置选择基于 XSD 的名称空间,并将其与使用注释进行配置的几个对象结合在一起。两者尽可能地提供一致的命名。由 XSD 模式定义的 XML 元素与注释的名称匹配,并且这些 XML 元素的属性与注释属性的名称匹配。你也可以直接使用 API,但是我们希望大多数开发人员选择一个更高级别的选项,或者基于名称空间的配置和注释驱动的配置的组合。
# 名称空间支持
你可以使用 XML 元素配置 Spring 集成组件,这些 XML 元素直接映射到 Enterprise 集成的术语和概念。在许多情况下,元素名与*Enterprise 整合模式 * (opens new window)书中的元素名匹配。
要在你的 Spring 配置文件中启用 Spring 集成的核心名称空间支持,请在你的顶级“bean”元素中添加以下名称空间引用和模式映射:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd">
(我们强调了 Spring 集成所特有的线条。)
你可以在“xmlns:”之后选择任何名称。为了清晰起见,我们使用int
(Integration 的缩写),但你可能更喜欢另一个缩写。另一方面,如果你使用 XML 编辑器或 IDE 支持,那么自动完成功能的可用性可能会说服你保留较长的名称以使其更清晰。或者,你也可以创建使用 Spring 集成模式作为主要名称空间的配置文件,如下例所示:
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd">
(我们强调了 Spring 集成所特有的线条。)
当使用此替代方案时, Spring 集成元素不需要前缀。另一方面,如果在相同的配置文件中定义一个泛型 Spring Bean,则 Bean 元素需要一个前缀(<beans:bean …/>
)。由于模块化配置文件本身(基于责任层或体系结构层)通常是一个好主意,因此你可能会发现在以集成为中心的配置文件中使用后一种方法是合适的,因为在这些文件中很少需要泛型 bean。出于本文的目的,我们假设集成名称空间是主要的。
Spring 集成提供了许多其他名称空间。实际上,每个提供名称空间支持的适配器类型(JMS、文件等)都在一个单独的模式中定义其元素。为了使用这些元素,请添加带有xmlns
条目和相应的schemaLocation
映射的必要名称空间。例如,下面的根元素显示了其中的几个名称空间声明:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:int-jms="http://www.springframework.org/schema/integration/jms"
xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
xmlns:int-rmi="http://www.springframework.org/schema/integration/rmi"
xmlns:int-ws="http://www.springframework.org/schema/integration/ws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/file
https://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/integration/jms
https://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd
http://www.springframework.org/schema/integration/mail
https://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd
http://www.springframework.org/schema/integration/rmi
https://www.springframework.org/schema/integration/rmi/spring-integration-rmi.xsd
http://www.springframework.org/schema/integration/ws
https://www.springframework.org/schema/integration/ws/spring-integration-ws.xsd">
...
</beans>
本参考手册在相应章节中提供了各种元素的具体示例。在这里,需要认识的主要问题是每个名称空间 URI 和模式位置的命名的一致性。
# 配置任务调度程序
在 Spring 集成中,ApplicationContext
扮演着消息总线的中心角色,你只需要考虑几个配置选项。首先,你可能想要控制中心TaskScheduler
实例。你可以通过提供一个名为taskScheduler
的 Bean 来实现此目的。这也被定义为一个常数,如下所示:
IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME
默认情况下, Spring 集成依赖于ThreadPoolTaskScheduler
的实例,如 Spring 框架参考手册的任务执行和调度 (opens new window)部分所述。默认的TaskScheduler
会在有 10 个线程池的情况下自动启动,但请参见全局属性。如果你提供你自己的TaskScheduler
实例,那么你可以将“自动启动”属性设置为false
,或者提供你自己的池大小值。
当轮询使用者在其配置中提供显式的任务执行器引用时,处理程序方法的调用发生在执行器的线程池中,而不是主调度器池中。但是,当没有为端点的 Poller 提供任务执行器时,它会被主调度程序的一个线程调用。
不要在 Poller 线程上运行长时间运行的任务。 改为使用任务执行器, 如果有很多轮询端点,除非增加池大小,否则可能会导致线程短缺, ,轮询消费者的默认 receiveTimeout 为一秒。由于这一次的 poller 线程阻塞,我们建议你在存在许多这样的端点时使用任务执行器,再次避免饥饿。 或者,你可以减少 receiveTimeout 。 |
---|
如果一个端点的输入通道是一个基于队列的(也就是 pollable)通道,那么它就是一个轮询消费者。 事件驱动的消费者是那些拥有带有调度器而不是队列的输入通道的消费者(换句话说,它们是可下标的)。 这样的端点没有 poller 配置,因为它们的处理程序是直接调用的。 |
---|
在 JEE 容器中运行时,你可能需要使用 Spring 的TimerManagerTaskScheduler ,如所描述的here (opens new window),而不是默认的taskScheduler 。要这样做,请为你的环境定义一个具有适当 JNDI 名称的 Bean,如下例所示: <br/><bean id="taskScheduler" class="org.springframework.scheduling.concurrent.DefaultManagedTaskScheduler"><br/> <property name="jndiName" value="tm/MyTimerManager" /><br/> <property name="resourceRef" value="true" /><br/></bean><br/> |
---|
当在应用程序上下文中配置自定义TaskScheduler (如上面提到的DefaultManagedTaskScheduler )时,建议为它提供一个MessagePublishingErrorHandler (integrationMessagePublishingErrorHandler Bean),以便能够处理异常,因为ErrorMessage s 被发送到错误通道,正如框架提供的默认TaskScheduler Bean 所做的那样。 |
---|
有关更多信息,请参见错误处理。
# 全局属性
可以通过在 Classpath 上提供一个属性文件来覆盖某些全局框架属性。
默认属性可以在org.springframework.integration.context.IntegrationProperties
类中找到。下面的列表显示了默认值:
spring.integration.channels.autoCreate=true (1)
spring.integration.channels.maxUnicastSubscribers=0x7fffffff (2)
spring.integration.channels.maxBroadcastSubscribers=0x7fffffff (3)
spring.integration.taskScheduler.poolSize=10 (4)
spring.integration.messagingTemplate.throwExceptionOnLateReply=false (5)
spring.integration.readOnly.headers= (6)
spring.integration.endpoints.noAutoStartup= (7)
spring.integration.channels.error.requireSubscribers=true (8)
spring.integration.channels.error.ignoreFailures=true (9)
1 | 如果为真,则在应用程序上下文中未显式找到input-channel 实例时,将自动声明为DirectChannel 实例。 |
---|---|
2 | 设置默认的订阅人数,例如,一个DirectChannel 。它可以用来避免无意中订阅多个端点到同一个通道。 你可以通过设置 max-subscribers 属性在单个通道上覆盖它。 |
3 | 此属性提供了默认的订阅服务器数量,例如,PublishSubscribeChannel 。它可以用来避免无意中订阅超过预期数量的相同通道的端点。 你可以通过设置 max-subscribers 属性在单个通道上覆盖它。 |
4 | 默认情况下可用的线程数taskScheduler Bean。参见配置任务调度程序。 |
5 | 当true 时,到达网关回复通道的消息在网关不期望得到回复时(因为发送线程已超时或已收到回复)会引发异常。 |
6 | 用逗号分隔的消息头名称列表在头部复制操作期间,不应将其填充到Message 实例中。该列表由 DefaultMessageBuilderFactory Bean 使用并传播到IntegrationMessageHeaderAccessor 实例(参见[MessageHeaderAccessor API](./message.html#message-header-accessor)),用于通过MessageBuilder 生成消息(参见[theMessageBuilder Helper 类](./message-builder r=“108”))。只有MessageHeaders.ID 和MessageHeaders.TIMESTAMP 在消息生成过程中不被复制。自版本 4.3.2 起。 |
7 | 用逗号分隔的AbstractEndpoint Bean 名称模式(xxx* ,**xxx** ,*xxx 或xxx*yyy )的列表,在应用程序启动期间不应自动启动。你可以稍后通过 Control Bus 通过其 Bean 名称手动启动这些端点,(参见控制总线),它们在SmartLifecycleRoleController 中的作用(见端点角色),或者通过Lifecycle Bean 注入。你可以通过指定 auto-startup XML 注释或autoStartup 注释属性,或者通过在 Bean 定义中调用AbstractEndpoint.setAutoStartup() 显式覆盖此全局属性的效果。自 4.3.12 版本开始。 |
8 | 表示默认全局errorChannel 必须配置requireSubscribers 选项的布尔标志。自版本 5.4.3 起。 有关更多信息,请参见错误处理。 |
9 | 一个布尔标志,指示默认的全局errorChannel 必须忽略调度错误并将消息传递给下一个处理程序。自版本 5.5 起。 |
可以通过向 Classpath 中添加/META-INF/spring.integration.properties
文件或为org.springframework.integration.context.IntegrationProperties
实例添加IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME
Bean 来重写这些属性。你不需要提供所有属性——只需要提供你想要覆盖的那些属性。
从版本 5.1 开始,当org.springframework.integration
类别的DEBUG
逻辑级别打开时,所有合并的全局属性都会在应用程序上下文启动后打印在日志中。输出如下所示:
Spring Integration global properties:
spring.integration.endpoints.noAutoStartup=fooService*
spring.integration.taskScheduler.poolSize=20
spring.integration.channels.maxUnicastSubscribers=0x7fffffff
spring.integration.channels.autoCreate=true
spring.integration.channels.maxBroadcastSubscribers=0x7fffffff
spring.integration.readOnly.headers=
spring.integration.messagingTemplate.throwExceptionOnLateReply=true
# 注释支持
除了对配置消息端点的 XML 命名空间的支持外,还可以使用注释。首先, Spring 集成提供了类级@MessageEndpoint
作为原型注释,这意味着它本身用 Spring 的@Component
注释,因此通过 Spring 的组件扫描自动识别为 Bean 定义。
更重要的是各种方法级别的注释。它们表示带注释的方法能够处理消息。下面的示例演示了类级和方法级的注释:
@MessageEndpoint
public class FooService {
@ServiceActivator
public void processMessage(Message message) {
...
}
}
方法“处理”消息的确切含义取决于特定的注释。 Spring 集成中提供的注释包括:
@Aggregator
(见Aggregator)@Filter
(见Filter)@Router
(见Routers)@ServiceActivator
(见服务激活器)@Splitter
(见Splitter)@Transformer
(见变压器)@InboundChannelAdapter
(见通道适配器)@BridgeFrom
(见用 Java 配置配置桥)@BridgeTo
(见用 Java 配置配置桥)@MessagingGateway
(见消息传递网关)@IntegrationComponentScan
(参见[configuration and@EnableIntegration
](./overview.html#configuration-enable-integration))
如果将 XML 配置与注释结合使用,则不需要@MessageEndpoint 注释。,如果要从<service-activator/> 元素的ref 属性中配置 POJO 引用,则只能提供方法级别的注释。,在这种情况下,,即使在 <service-activator/> 元素上不存在方法级属性时,该注释也可以防止歧义。 |
---|
在大多数情况下,带注释的处理程序方法不应该要求Message
类型作为其参数。相反,方法参数类型可以匹配消息的有效负载类型,如下例所示:
public class ThingService {
@ServiceActivator
public void bar(Thing thing) {
...
}
}
当方法参数应该从MessageHeaders
中的值映射时,另一个选项是使用参数级别@Header
注释。通常,使用 Spring 集成注释注释的方法可以接受Message
本身、消息有效负载或头值(带有@Header
)作为参数。实际上,该方法可以接受一种组合,如下例所示:
public class ThingService {
@ServiceActivator
public void otherThing(String payload, @Header("x") int valueX, @Header("y") int valueY) {
...
}
}
你还可以使用@Headers
注释来提供所有消息头作为Map
,如下例所示:
public class ThingService {
@ServiceActivator
public void otherThing(String payload, @Headers Map<String, Object> headerMap) {
...
}
}
注释的值也可以是一个 SPEL 表达式(例如,someHeader.toUpperCase() ),当你希望在注入标头值之前对标头值进行操作时,该表达式非常有用,它还提供了一个可选的 required 属性,它指定属性值是否必须在标题中可用。required 属性的默认值是true 。 |
---|
对于其中的几个注释,当消息处理方法返回一个非空值时,端点将尝试发送一个答复。这在两种配置选项(名称空间和注释)中都是一致的,因为使用了这样的端点输出通道(如果可用的话),并且使用REPLY_CHANNEL
消息头值作为后备。
端点上的输出通道和应答通道消息头的组合实现了一种流水线方法,在这种方法中,多个组件具有一个输出通道,而最终组件允许将应答消息转发到应答通道(如原始请求消息中指定的那样)。,换句话说, ,最后的组件依赖于原始发送者提供的信息,并可以动态地支持任意数量的客户端作为结果。 这是回邮地址 (opens new window)模式的一个示例。 |
---|
除了这里显示的示例之外,这些注释还支持inputChannel
和outputChannel
属性,如下例所示:
@Service
public class ThingService {
@ServiceActivator(inputChannel="input", outputChannel="output")
public void otherThing(String payload, @Headers Map<String, Object> headerMap) {
...
}
}
这些注释的处理创建了与相应的 XML 组件相同的 bean—AbstractEndpoint
实例和MessageHandler
实例(或入站通道适配器的MessageSource
实例)。参见[关于@Bean
方法的注释]。 Bean 名称是从以下模式生成的:[componentName].[methodName].[decapitalizedAnnotationClassShortName]
。在前面的示例中, Bean 名称为AbstractEndpoint
的thingService.otherThing.serviceActivator
,而与.handler
(.source
) Bean 的后缀为MessageHandler
(MessageSource
)。可以使用@EndpointId
注释以及这些消息传递注释来定制这样的名称。MessageHandler
实例(MessageSource
实例)也有资格被消息历史跟踪。
从版本 4.0 开始,所有消息传递注释都提供SmartLifecycle
选项(autoStartup
和phase
),以允许对应用程序上下文初始化进行端点生命周期控制。它们分别默认为true
和0
。要更改端点的状态(例如start()
或stop()
),可以通过使用BeanFactory
(或自动布线)获得对端点 Bean 的引用并调用方法。或者,你可以向Control Bus
发送命令消息(参见控制总线)。为了这些目的,你应该使用上一段前面提到的beanName
。
在解析所提及的注释(当没有配置特定的通道 Bean 时)之后自动创建的通道和相应的消费者端点,在上下文初始化的末尾附近被声明为 bean。 这些 beanCAN在其他服务中被自动连线,但它们必须用 @Lazy 注释标记,因为这些定义通常在正常的自动布线处理过程中还不可用。<br/>@Autowired<br/>@Lazy<br/>@Qualifier("someChannel")<br/>MessageChannel someChannel;<br/>...<br/><br/>@Bean<br/>Thing1 dependsOnSPCA(@Qualifier("someInboundAdapter") @Lazy SourcePollingChannelAdapter someInboundAdapter) {<br/> ...<br/>}<br/> |
---|
# 使用@Poller
注释
Spring Integration4.0 之前,消息传递注释要求inputChannel
是对SubscribableChannel
的引用。对于PollableChannel
实例,需要一个<int:bridge/>
元素来配置<int:poller/>
并使复合端点为PollingConsumer
。版本 4.0 引入了@Poller
注释,以允许直接在消息传递注释上配置poller
属性,如下例所示:
public class AnnotationService {
@Transformer(inputChannel = "input", outputChannel = "output",
poller = @Poller(maxMessagesPerPoll = "${poller.maxMessagesPerPoll}", fixedDelay = "${poller.fixedDelay}"))
public String handle(String payload) {
...
}
}
@Poller
注释仅提供简单的PollerMetadata
选项。你可以使用属性占位符配置@Poller
注释的属性(maxMessagesPerPoll
、fixedDelay
、fixedRate
和cron
)。此外,从版本 5.1 开始,还提供了receiveTimeout
s 的PollingConsumer
选项。如果需要提供更多的轮询选项(例如,transaction
,advice-chain
,error-handler
,以及其他),则应将PollerMetadata
配置为泛型 Bean,并将其 Bean 名称用作@Poller
的value
属性。在这种情况下,不允许其他属性(它们必须在PollerMetadata
Bean 上指定)。注意,如果inputChannel
是PollableChannel
,并且没有配置@Poller
,则使用默认的PollerMetadata
(如果它存在于应用程序上下文中)。要通过使用@Configuration
注释来声明默认的 Poller,请使用类似于以下示例的代码:
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata defaultPoller() {
PollerMetadata pollerMetadata = new PollerMetadata();
pollerMetadata.setTrigger(new PeriodicTrigger(10));
return pollerMetadata;
}
下面的示例展示了如何使用默认的 Poller:
public class AnnotationService {
@Transformer(inputChannel = "aPollableChannel", outputChannel = "output")
public String handle(String payload) {
...
}
}
下面的示例展示了如何使用命名的 Poller:
@Bean
public PollerMetadata myPoller() {
PollerMetadata pollerMetadata = new PollerMetadata();
pollerMetadata.setTrigger(new PeriodicTrigger(1000));
return pollerMetadata;
}
下面的示例显示了一个使用默认 Poller 的端点:
public class AnnotationService {
@Transformer(inputChannel = "aPollableChannel", outputChannel = "output"
poller = @Poller("myPoller"))
public String handle(String payload) {
...
}
}
从版本 4.3.3 开始,@Poller
注释具有errorChannel
属性,以便更轻松地配置底层MessagePublishingErrorHandler
。此属性在<poller>
XML 组件中扮演与error-channel
相同的角色。有关更多信息,请参见端点命名空间支持。
消息传递注释上的poller()
属性与reactive()
属性是互斥的。有关更多信息,请参见下一节。
# 使用@Reactive
注释
ReactiveStreamsConsumer
自版本 5.0 以来一直存在,但它仅在端点的输入通道是FluxMessageChannel
(或任何org.reactivestreams.Publisher
实现)时才应用。从版本 5.3 开始,当目标消息处理程序是独立于输入通道类型的ReactiveMessageHandler
时,框架也会创建它的实例。对于从版本 5.5 开始的所有消息传递注释,都引入了@Reactive
子注释(类似于上面提到的@Poller
)。它接受一个可选的Function<? super Flux<Message<?>>, ? extends Publisher<Message<?>>>
Bean 引用,并且独立于输入通道类型和消息处理程序,将目标端点转换为ReactiveStreamsConsumer
实例。该函数使用来自Flux.transform()
操作符的一些自定义(publishOn()
,doOnNext()
,log()
,retry()
等)在来自输入通道的反应流上应用。
下面的示例演示了如何将发布线程从独立于最终订阅者和生产者的输入通道更改为DirectChannel
:
@Bean
public Function<Flux<?>, Flux<?>> publishOnCustomizer() {
return flux -> flux.publishOn(Schedulers.parallel());
}
@ServiceActivator(inputChannel = "directChannel", reactive = @Reactive("publishOnCustomizer"))
public void handleReactive(String payload) {
...
}
消息传递注释上的reactive()
属性与poller()
属性是互斥的。有关更多信息,请参见[使用@Poller
注释](#configuration-using-poller-annotation)和反应流支持。
# 使用@InboundChannelAdapter
注释
版本 4.0 引入了@InboundChannelAdapter
方法级注释。它为带注释的方法基于MethodInvokingMessageSource
生成SourcePollingChannelAdapter
集成组件。该注释类似于<int:inbound-channel-adapter>
XML 组件,并且具有相同的限制:该方法不能具有参数,并且返回类型不能是void
。它有两个属性:value
(要求的MessageChannel
Bean name)和poller
(可选的@Poller
注释,如前面描述的)。如果需要提供一些MessageHeaders
,使用Message<?>
返回类型并使用MessageBuilder
来构建Message<?>
。使用MessageBuilder
可以配置MessageHeaders
。下面的示例展示了如何使用@InboundChannelAdapter
注释:
@InboundChannelAdapter("counterChannel")
public Integer count() {
return this.counter.incrementAndGet();
}
@InboundChannelAdapter(value = "fooChannel", poller = @Poller(fixed-rate = "5000"))
public String foo() {
return "foo";
}
版本 4.3 引入了channel
的value
注释属性的别名,以提供更好的源代码可读性。同样,目标MessageChannel
Bean 在SourcePollingChannelAdapter
中通过第一个outputChannelName
调用上提供的名称(由outputChannelName
选项设置)进行解析,而不是在初始化阶段。它允许“后期绑定”逻辑:从消费者的角度来看,目标MessageChannel
Bean 的创建和注册要比@InboundChannelAdapter
解析阶段晚一点。
第一个示例要求在应用程序上下文的其他地方声明了默认的 Poller。
使用@MessagingGateway
注释
参见[@MessagingGateway
注释](./gateway.html#messaging-gateway-annotation)。
# 使用@IntegrationComponentScan
注释
标准的 Spring 框架@ComponentScan
注释不扫描接口中的原型@Component
注释。为了克服这个限制并允许@MessagingGateway
的配置(参见[@MessagingGateway
注释](./gateway.html#messaging-gateway-annotation)),我们引入了@IntegrationComponentScan
机制。此注释必须与@Configuration
注释一起放置,并进行自定义以定义其扫描选项,例如basePackages
和basePackageClasses
。在这种情况下,所有发现的带有@MessagingGateway
注释的接口都被解析并注册为GatewayProxyFactoryBean
实例。所有其他基于类的组件都由标准@ComponentScan
解析。
# 消息传递元注释
从版本 4.0 开始,所有消息传递注释都可以配置为元注释,并且所有用户定义的消息传递注释都可以定义相同的属性来覆盖其默认值。此外,元注释可以按层次进行配置,如下例所示:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ServiceActivator(inputChannel = "annInput", outputChannel = "annOutput")
public @interface MyServiceActivator {
String[] adviceChain = { "annAdvice" };
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator
public @interface MyServiceActivator1 {
String inputChannel();
String outputChannel();
}
...
@MyServiceActivator1(inputChannel = "inputChannel", outputChannel = "outputChannel")
public Object service(Object payload) {
...
}
分层配置元注释使用户可以为各种属性设置默认值,并使 Framework Java 依赖与用户注释隔离,从而避免在用户类中使用它们。如果框架找到了一个具有框架元注释的用户注释的方法,则将其视为直接使用框架注释对该方法进行了注释。
# 方法上的注释
从版本 4.0 开始,你可以在@Configuration
类中的@Bean
方法定义上配置消息注释,以基于 bean 而不是方法生成消息端点。当@Bean
定义是“开箱即用”MessageHandler
实例(AggregatingMessageHandler
,DefaultMessageSplitter
,以及其他),Transformer
实例(JsonToObjectTransformer
,ClaimCheckOutTransformer
,以及其他),以及MessageSource
实例(FileReadingMessageSource
,RedisStoreMessageSource
,以及其他)时,它是有用的。下面的示例展示了如何使用带有@Bean
注释的消息传递注释:
@Configuration
@EnableIntegration
public class MyFlowConfiguration {
@Bean
@InboundChannelAdapter(value = "inputChannel", poller = @Poller(fixedDelay = "1000"))
public MessageSource<String> consoleSource() {
return CharacterStreamReadingMessageSource.stdin();
}
@Bean
@Transformer(inputChannel = "inputChannel", outputChannel = "httpChannel")
public ObjectToMapTransformer toMapTransformer() {
return new ObjectToMapTransformer();
}
@Bean
@ServiceActivator(inputChannel = "httpChannel")
public MessageHandler httpHandler() {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("https://foo/service");
handler.setExpectedResponseType(String.class);
handler.setOutputChannelName("outputChannel");
return handler;
}
@Bean
@ServiceActivator(inputChannel = "outputChannel")
public LoggingHandler loggingHandler() {
return new LoggingHandler("info");
}
}
版本 5.0 引入了对@Bean
注释的@InboundChannelAdapter
的支持,该注释返回java.util.function.Supplier
,可以生成 POJO 或Message
。下面的示例展示了如何使用该组合:
@Configuration
@EnableIntegration
public class MyFlowConfiguration {
@Bean
@InboundChannelAdapter(value = "inputChannel", poller = @Poller(fixedDelay = "1000"))
public Supplier<String> pojoSupplier() {
return () -> "foo";
}
@Bean
@InboundChannelAdapter(value = "inputChannel", poller = @Poller(fixedDelay = "1000"))
public Supplier<Message<String>> messageSupplier() {
return () -> new GenericMessage<>("foo");
}
}
元注释规则也适用于@Bean
方法(@MyServiceActivator
注释前面描述的可以应用于@Bean
定义)。
在使用者@Bean 定义上使用这些注释时,如果 Bean 定义返回适当的MessageHandler (取决于注释类型),则必须设置属性(例如outputChannel ,requiresReply ,order ,以及其他属性),在MessageHandler``@Bean 定义本身上。只使用以下注释属性: adviceChain 、autoStartup 、inputChannel 、phase 和poller 。所有其他属性都是处理程序的。 |
---|
Bean 名称是通过以下算法生成的: |
---|
MessageHandler
(MessageSource
)@Bean
从@Bean
上的方法名或name
属性获得自己的标准名称。这就好像在@Bean
方法上没有消息注释一样。生成
AbstractEndpoint
Bean 名称的模式如下:[configurationComponentName].[methodName].[decapitalizedAnnotationClassShortName]
。例如,SourcePollingChannelAdapter
的consoleSource()
定义显示在前面的端点得到一个 Bean 名myFlowConfiguration.consoleSource.inboundChannelAdapter
。另见Endpoint Bean Names。
当在@Bean 定义上使用这些注释时,inputChannel 必须引用已声明的 Bean。在这种情况下,通道不会自动声明。 |
---|
使用 Java 配置,你可以在@Bean 方法级别上使用任何@Conditional (例如,@Profile )的定义来跳过 Bean 注册的某些条件原因。下面的示例展示了如何这样做: <br/>@Bean<br/>@ServiceActivator(inputChannel = "skippedChannel")<br/>@Profile("thing")<br/>public MessageHandler skipped() {<br/> return System.out::println;<br/>}<br/> 连同现有的容器逻辑一起,消息传递端点 Bean(基于@ServiceActivator 注释)也未注册。 |
---|
# 创建带有注释的桥
从版本 4.0 开始,Java Configuration 提供@BridgeFrom
和@BridgeTo``@Bean
方法注释,以便在MessageChannel
类中标记MessageChannel
bean。这些确实是为了完整性而存在的,提供了一种方便的机制来声明BridgeHandler
及其消息端点配置:
@Bean
public PollableChannel bridgeFromInput() {
return new QueueChannel();
}
@Bean
@BridgeFrom(value = "bridgeFromInput", poller = @Poller(fixedDelay = "1000"))
public MessageChannel bridgeFromOutput() {
return new DirectChannel();
}
@Bean
public QueueChannel bridgeToOutput() {
return new QueueChannel();
}
@Bean
@BridgeTo("bridgeToOutput")
public MessageChannel bridgeToInput() {
return new DirectChannel();
}
你也可以将这些注释用作元注释。
# 通知带注释的端点
# 消息映射规则和约定
Spring 集成实现了一种灵活的功能,通过依赖一些默认规则和定义某些约定,在不提供额外配置的情况下将消息映射到方法及其参数。以下各节中的示例阐明了这些规则。
# 示例场景
下面的示例显示了一个未注释的参数(对象或原语),它不是具有非空返回类型的Map
或Properties
对象:
public String doSomething(Object o);
输入参数是消息有效负载。如果参数类型与消息有效负载不兼容,则尝试使用 Spring 3.0 提供的转换服务对其进行转换。返回值被合并为返回消息的有效负载。
下面的示例显示了一个未注释的参数(对象或原语),该参数不是Map
或Properties
,返回类型为Message
:
public Message doSomething(Object o);
输入参数是消息有效负载。如果参数类型与消息有效负载不兼容,则尝试使用 Spring 3.0 提供的转换服务对其进行转换。返回值是一个新构造的消息,它被发送到下一个目的地。
下面的示例显示了一个参数,它是具有任意对象或原始返回类型的消息(或其子类之一):
public int doSomething(Message msg);
输入参数本身是Message
。返回值成为发送到下一个目的地的Message
的有效负载。
下面的示例显示了一个参数Message
(或其子类之一),并以Message
(或其子类之一)作为返回类型:
public Message doSomething(Message msg);
输入参数本身是Message
。返回值是一个新构造的Message
,它被发送到下一个目标。
下面的示例显示了类型为Map
或Properties
的单个参数,其返回类型为Message
:
public Message doSomething(Map m);
这个有点有趣。虽然一开始它看起来像是直接映射到消息头的简单映射,但是总是优先考虑Message
有效负载。这意味着,如果Message
有效载荷类型为Map
,则此输入参数表示Message
有效载荷。但是,如果Message
有效负载不是Map
类型,则转换服务不尝试转换有效负载,并且将输入参数映射到消息头。
下面的示例显示了两个参数,其中一个是不是Map
或Properties
对象的任意类型(对象或原语),另一个是Map
或Properties
类型(无论返回如何):
public Message doSomething(Map h, <T> t);
这个组合包含两个输入参数,其中一个参数的类型为Map
。将非Map
参数(无论顺序如何)映射到Message
有效负载,并将Map
或Properties
(无论顺序如何)映射到消息头,从而为你提供了一种与Message
结构进行交互的良好的 POJO 方式。
下面的示例不显示参数(无论返回的是什么):
public String doSomething();
此消息处理程序方法是基于发送到此处理程序所连接的输入通道的消息而调用的。但是没有映射Message
数据,因此使Message
充当事件或触发器来调用处理程序。根据规则前面描述的映射输出。
下面的示例不显示参数和 void 返回:
public void soSomething();
这个示例与前面的示例相同,但不产生输出。
# 基于注释的映射
基于注释的映射是将消息映射到方法的最安全、最不模糊的方法。下面的示例展示了如何显式地将方法映射到标头:
public String doSomething(@Payload String s, @Header("someheader") String b)
正如你稍后所看到的,如果没有注释,这个签名将导致一个模棱两可的条件。然而,通过显式地将第一个参数映射到Message
有效负载,并将第二个参数映射到someheader
消息头的值,我们避免了任何歧义。
下面的示例与前面的示例几乎相同:
public String doSomething(@Payload String s, @RequestParam("something") String b)
@RequestMapping
或任何其他非 Spring 积分映射注释是不相关的,因此被忽略,使第二个参数未映射。尽管第二个参数可以很容易地映射到一个有效负载,但只能有一个有效负载。因此,注释避免了该方法的模棱两可。
下面的示例展示了另一种类似的方法,如果不是通过注释来澄清意图,这种方法将是模棱两可的:
public String foo(String s, @Header("foo") String b)
唯一的区别是,第一个参数隐式地映射到消息有效负载。
下面的示例展示了另一个签名,该签名在没有注释的情况下肯定会被视为模棱两可,因为它有两个以上的参数:
public String soSomething(@Headers Map m, @Header("something") Map f, @Header("someotherthing") String bar)
这个示例将特别有问题,因为它的两个参数是Map
实例。然而,使用基于注释的映射,可以很容易地避免歧义。在这个示例中,第一个参数映射到所有消息头,而第二个和第三个参数映射到名为“Something”和“Somethothing”的消息头的值。有效载荷没有被映射到任何参数。
# 复杂情景
下面的示例使用了多个参数:
在确定适当的映射时,多个参数可能会产生很多歧义。一般的建议是用@Payload
、@Header
和@Headers
注释方法参数。本节中的示例显示了导致引发异常的模棱两可的条件。
public String doSomething(String s, int i)
这两个参数的权重相等。因此,没有办法确定哪一个是有效载荷。
下面的示例显示了一个类似的问题,只包含三个参数:
public String foo(String s, Map m, String b)
尽管映射可以很容易地映射到消息头,但无法确定如何处理这两个字符串参数。
下面的示例展示了另一个模棱两可的方法:
public String foo(Map m, Map f)
尽管有人可能认为,一个Map
可以映射到消息有效负载,另一个可以映射到消息头,但我们不能依赖顺序。
具有多个不是(map,<T>)的方法参数和未注释的参数的任何方法签名都会导致模棱两可的情况并触发异常。 |
---|
下一组示例都展示了导致歧义的多种方法。
具有多个方法的消息处理程序是基于前面(在示例中)描述的相同规则进行映射的。然而,有些情况看起来仍然令人困惑。
下面的示例展示了具有合法(可映射和明确的)签名的多个方法:
public class Something {
public String doSomething(String str, Map m);
public String doSomething(Map m);
}
(无论这些方法的名称是相同的,还是不同的,都没有区别)。Message
可以映射到任意一个方法。当消息有效负载可以映射到str
并且消息头可以映射到m
时,将调用第一个方法。第二种方法也可以通过仅将消息头映射到m
而成为候选方法。更糟糕的是,这两种方法名称相同。首先,由于以下配置,这可能看起来很模糊:
<int:service-activator input-channel="input" output-channel="output" method="doSomething">
<bean class="org.things.Something"/>
</int:service-activator>
它之所以有效,是因为映射首先基于有效负载,然后才是其他所有的东西。换句话说,第一个参数可以映射到有效负载的方法优先于所有其他方法。
现在考虑另一个例子,它产生了一个真正不明确的条件:
public class Something {
public String doSomething(String str, Map m);
public String doSomething(String str);
}
这两个方法都有可以映射到消息有效负载的签名。他们也有相同的名字。这样的处理程序方法将触发异常。但是,如果方法名称不同,则可能会使用method
属性来影响映射(如下一个示例所示)。下面的示例显示了具有两个不同方法名称的相同示例:
public class Something {
public String doSomething(String str, Map m);
public String doSomethingElse(String str);
}
下面的示例展示了如何使用method
属性来指定映射:
<int:service-activator input-channel="input" output-channel="output" method="doSomethingElse">
<bean class="org.bar.Foo"/>
</int:service-activator>
因为配置显式地映射了doSomethingElse
方法,所以我们消除了歧义。
← Spring 集成中的安全性 测试支持 →