# 配置 ## 配置 Spring 集成提供了许多配置选项。你选择哪一种选择取决于你的特殊需求,以及你更喜欢工作的级别。与 Spring 框架一般一样,你可以混合和匹配各种技术以适应手头的问题。例如,你可以为大多数配置选择基于 XSD 的名称空间,并将其与使用注释进行配置的几个对象结合在一起。两者尽可能地提供一致的命名。由 XSD 模式定义的 XML 元素与注释的名称匹配,并且这些 XML 元素的属性与注释属性的名称匹配。你也可以直接使用 API,但是我们希望大多数开发人员选择一个更高级别的选项,或者基于名称空间的配置和注释驱动的配置的组合。 ### 名称空间支持 你可以使用 XML 元素配置 Spring 集成组件,这些 XML 元素直接映射到 Enterprise 集成的术语和概念。在许多情况下,元素名与[*Enterprise 整合模式 *](https://www.enterpriseintegrationpatterns.com/)书中的元素名匹配。 要在你的 Spring 配置文件中启用 Spring 集成的核心名称空间支持,请在你的顶级“bean”元素中添加以下名称空间引用和模式映射: ``` ``` (我们强调了 Spring 集成所特有的线条。) 你可以在“xmlns:”之后选择任何名称。为了清晰起见,我们使用`int`(Integration 的缩写),但你可能更喜欢另一个缩写。另一方面,如果你使用 XML 编辑器或 IDE 支持,那么自动完成功能的可用性可能会说服你保留较长的名称以使其更清晰。或者,你也可以创建使用 Spring 集成模式作为主要名称空间的配置文件,如下例所示: ``` ``` (我们强调了 Spring 集成所特有的线条。) 当使用此替代方案时, Spring 集成元素不需要前缀。另一方面,如果在相同的配置文件中定义一个泛型 Spring Bean,则 Bean 元素需要一个前缀(``)。由于模块化配置文件本身(基于责任层或体系结构层)通常是一个好主意,因此你可能会发现在以集成为中心的配置文件中使用后一种方法是合适的,因为在这些文件中很少需要泛型 bean。出于本文的目的,我们假设集成名称空间是主要的。 Spring 集成提供了许多其他名称空间。实际上,每个提供名称空间支持的适配器类型(JMS、文件等)都在一个单独的模式中定义其元素。为了使用这些元素,请添加带有`xmlns`条目和相应的`schemaLocation`映射的必要名称空间。例如,下面的根元素显示了其中的几个名称空间声明: ``` ... ``` 本参考手册在相应章节中提供了各种元素的具体示例。在这里,需要认识的主要问题是每个名称空间 URI 和模式位置的命名的一致性。 ### 配置任务调度程序 在 Spring 集成中,`ApplicationContext`扮演着消息总线的中心角色,你只需要考虑几个配置选项。首先,你可能想要控制中心`TaskScheduler`实例。你可以通过提供一个名为`taskScheduler`的 Bean 来实现此目的。这也被定义为一个常数,如下所示: ``` IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME ``` 默认情况下, Spring 集成依赖于`ThreadPoolTaskScheduler`的实例,如 Spring 框架参考手册的[任务执行和调度](https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#scheduling)部分所述。默认的`TaskScheduler`会在有 10 个线程池的情况下自动启动,但请参见[全局属性](#global-properties)。如果你提供你自己的`TaskScheduler`实例,那么你可以将“自动启动”属性设置为`false`,或者提供你自己的池大小值。 当轮询使用者在其配置中提供显式的任务执行器引用时,处理程序方法的调用发生在执行器的线程池中,而不是主调度器池中。但是,当没有为端点的 Poller 提供任务执行器时,它会被主调度程序的一个线程调用。 | |不要在 Poller 线程上运行长时间运行的任务。
改为使用任务执行器,
如果有很多轮询端点,除非增加池大小,否则可能会导致线程短缺,
,轮询消费者的默认`receiveTimeout`为一秒。
由于这一次的 poller 线程阻塞,我们建议你在存在许多这样的端点时使用任务执行器,再次避免饥饿。
或者,你可以减少`receiveTimeout`。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |如果一个端点的输入通道是一个基于队列的(也就是 pollable)通道,那么它就是一个轮询消费者。
事件驱动的消费者是那些拥有带有调度器而不是队列的输入通道的消费者(换句话说,它们是可下标的)。
这样的端点没有 poller 配置,因为它们的处理程序是直接调用的。| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |在 JEE 容器中运行时,你可能需要使用 Spring 的`TimerManagerTaskScheduler`,如所描述的[here](https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#scheduling-task-scheduler-implementations),而不是默认的`taskScheduler`。
要这样做,请为你的环境定义一个具有适当 JNDI 名称的 Bean,如下例所示:

```




```| |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |当在应用程序上下文中配置自定义`TaskScheduler`(如上面提到的`DefaultManagedTaskScheduler`)时,建议为它提供一个`MessagePublishingErrorHandler`(`integrationMessagePublishingErrorHandler` Bean),以便能够处理异常,因为`ErrorMessage`s 被发送到错误通道,正如框架提供的默认`TaskScheduler` Bean 所做的那样。| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 有关更多信息,请参见[错误处理](./error-handling.html#error-handling)。 ### 全局属性 可以通过在 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。
参见[配置任务调度程序](#namespace-taskscheduler)。| |**5**|当`true`时,到达网关回复通道的消息在网关不期望得到回复时(因为发送线程已超时或已收到回复)会引发异常。| |**6**|用逗号分隔的消息头名称列表在头部复制操作期间,不应将其填充到`Message`实例中。
该列表由`DefaultMessageBuilderFactory` Bean 使用并传播到`IntegrationMessageHeaderAccessor`实例(参见[`MessageHeaderAccessor`API](./message.html#message-header-accessor)),用于通过`MessageBuilder`生成消息(参见[the`MessageBuilder`Helper 类](./message-builder r=“108”))。只有`MessageHeaders.ID`和`MessageHeaders.TIMESTAMP`在消息生成过程中不被复制。
自版本 4.3.2 起。| |**7**|用逗号分隔的`AbstractEndpoint` Bean 名称模式(`xxx*`,`**xxx**`**,`*xxx`** 或`xxx*yyy`)的列表,在应用程序启动期间不应自动启动。
你可以稍后通过`Control Bus`通过其 Bean 名称手动启动这些端点,(参见[控制总线](./control-bus.html#control-bus)),它们在`SmartLifecycleRoleController`中的作用(见[端点角色](./endpoint.html#endpoint-roles)),或者通过`Lifecycle` Bean 注入。
你可以通过指定`auto-startup`XML 注释或`autoStartup`注释属性,或者通过在 Bean 定义中调用`AbstractEndpoint.setAutoStartup()`显式覆盖此全局属性的效果。
自 4.3.12 版本开始。| |**8**|表示默认全局`errorChannel`必须配置`requireSubscribers`选项的布尔标志。
自版本 5.4.3 起。
有关更多信息,请参见[错误处理](./error-handling.html#error-handling)。| |**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](./aggregator.html#aggregator)) * `@Filter`(见[Filter](./filter.html#filter)) * `@Router`(见[Routers](./router.html#router)) * `@ServiceActivator`(见[服务激活器](./service-activator.html#service-activator)) * `@Splitter`(见[Splitter](./splitter.html#splitter)) * `@Transformer`(见[变压器](./transformer.html#transformer)) * `@InboundChannelAdapter`(见[通道适配器](./channel-adapter.html#channel-adapter)) * `@BridgeFrom`(见[用 Java 配置配置桥](./bridge.html#bridge-annot)) * `@BridgeTo`(见[用 Java 配置配置桥](./bridge.html#bridge-annot)) * `@MessagingGateway`(见[消息传递网关](./gateway.html#gateway)) * `@IntegrationComponentScan`(参见[configuration and`@EnableIntegration`](./overview.html#configuration-enable-integration)) | |如果将 XML 配置与注释结合使用,则不需要`@MessageEndpoint`注释。,如果要从``元素的`ref`属性中配置 POJO 引用,则只能提供方法级别的注释。,在这种情况下,
,即使在``元素上不存在方法级属性时,该注释也可以防止歧义。| |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 在大多数情况下,带注释的处理程序方法不应该要求`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 headerMap) { ... } } ``` | |注释的值也可以是一个 SPEL 表达式(例如,`someHeader.toUpperCase()`),当你希望在注入标头值之前对标头值进行操作时,该表达式非常有用,
它还提供了一个可选的`required`属性,它指定属性值是否必须在标题中可用。
`required`属性的默认值是`true`。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 对于其中的几个注释,当消息处理方法返回一个非空值时,端点将尝试发送一个答复。这在两种配置选项(名称空间和注释)中都是一致的,因为使用了这样的端点输出通道(如果可用的话),并且使用`REPLY_CHANNEL`消息头值作为后备。 | |端点上的输出通道和应答通道消息头的组合实现了一种流水线方法,在这种方法中,多个组件具有一个输出通道,而最终组件允许将应答消息转发到应答通道(如原始请求消息中指定的那样)。,换句话说,
,最后的组件依赖于原始发送者提供的信息,并可以动态地支持任意数量的客户端作为结果。
这是[回邮地址](https://www.enterpriseintegrationpatterns.com/ReturnAddress.html)模式的一个示例。| |---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 除了这里显示的示例之外,这些注释还支持`inputChannel`和`outputChannel`属性,如下例所示: ``` @Service public class ThingService { @ServiceActivator(inputChannel="input", outputChannel="output") public void otherThing(String payload, @Headers Map headerMap) { ... } } ``` 这些注释的处理创建了与相应的 XML 组件相同的 bean—`AbstractEndpoint`实例和`MessageHandler`实例(或入站通道适配器的`MessageSource`实例)。参见[关于`@Bean`方法的注释]。 Bean 名称是从以下模式生成的:`[componentName].[methodName].[decapitalizedAnnotationClassShortName]`。在前面的示例中, Bean 名称为`AbstractEndpoint`的`thingService.otherThing.serviceActivator`,而与`.handler`(`.source`) Bean 的后缀为`MessageHandler`(`MessageSource`)。可以使用`@EndpointId`注释以及这些消息传递注释来定制这样的名称。`MessageHandler`实例(`MessageSource`实例)也有资格被[消息历史](./message-history.html#message-history)跟踪。 从版本 4.0 开始,所有消息传递注释都提供`SmartLifecycle`选项(`autoStartup`和`phase`),以允许对应用程序上下文初始化进行端点生命周期控制。它们分别默认为`true`和`0`。要更改端点的状态(例如` start()`或`stop()`),可以通过使用`BeanFactory`(或自动布线)获得对端点 Bean 的引用并调用方法。或者,你可以向`Control Bus`发送命令消息(参见[控制总线](./control-bus.html#control-bus))。为了这些目的,你应该使用上一段前面提到的`beanName`。 | |在解析所提及的注释(当没有配置特定的通道 Bean 时)之后自动创建的通道和相应的消费者端点,在上下文初始化的末尾附近被声明为 bean。
这些 bean**CAN**在其他服务中被自动连线,但它们必须用`@Lazy`注释标记,因为这些定义通常在正常的自动布线处理过程中还不可用。

```
@Autowired
@Lazy
@Qualifier("someChannel")
MessageChannel someChannel;
...

@Bean
Thing1 dependsOnSPCA(@Qualifier("someInboundAdapter") @Lazy SourcePollingChannelAdapter someInboundAdapter) {
...
}
```| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| #### 使用`@Poller`注释 Spring Integration4.0 之前,消息传递注释要求`inputChannel`是对`SubscribableChannel`的引用。对于`PollableChannel`实例,需要一个``元素来配置``并使复合端点为`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`。此属性在``XML 组件中扮演与`error-channel`相同的角色。有关更多信息,请参见[端点命名空间支持](./endpoint.html#endpoint-namespace)。 消息传递注释上的`poller()`属性与`reactive()`属性是互斥的。有关更多信息,请参见下一节。 #### 使用`@Reactive`注释 `ReactiveStreamsConsumer`自版本 5.0 以来一直存在,但它仅在端点的输入通道是`FluxMessageChannel`(或任何`org.reactivestreams.Publisher`实现)时才应用。从版本 5.3 开始,当目标消息处理程序是独立于输入通道类型的`ReactiveMessageHandler`时,框架也会创建它的实例。对于从版本 5.5 开始的所有消息传递注释,都引入了`@Reactive`子注释(类似于上面提到的`@Poller`)。它接受一个可选的`Function>, ? extends Publisher>>` Bean 引用,并且独立于输入通道类型和消息处理程序,将目标端点转换为`ReactiveStreamsConsumer`实例。该函数使用来自`Flux.transform()`操作符的一些自定义(`publishOn()`,`doOnNext()`,`log()`,`retry()`等)在来自输入通道的反应流上应用。 下面的示例演示了如何将发布线程从独立于最终订阅者和生产者的输入通道更改为`DirectChannel`: ``` @Bean public Function, 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)和[反应流支持](./reactive-streams.html#reactive-streams)。 #### 使用`@InboundChannelAdapter`注释 版本 4.0 引入了`@InboundChannelAdapter`方法级注释。它为带注释的方法基于`MethodInvokingMessageSource`生成`SourcePollingChannelAdapter`集成组件。该注释类似于``XML 组件,并且具有相同的限制:该方法不能具有参数,并且返回类型不能是`void`。它有两个属性:`value`(要求的`MessageChannel` Bean name)和`poller`(可选的`@Poller`注释,如[前面描述的](#configuration-using-poller-annotation))。如果需要提供一些`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 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 pojoSupplier() { return () -> "foo"; } @Bean @InboundChannelAdapter(value = "inputChannel", poller = @Poller(fixedDelay = "1000")) public Supplier> messageSupplier() { return () -> new GenericMessage<>("foo"); } } ``` 元注释规则也适用于`@Bean`方法(`@MyServiceActivator`注释[前面描述的](#meta-annotations)可以应用于`@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()`定义[显示在前面](#annotations_on_beans)的端点得到一个 Bean 名`myFlowConfiguration.consoleSource.inboundChannelAdapter`。另见[Endpoint Bean Names](./overview.html#endpoint-bean-names)。 | |当在`@Bean`定义上使用这些注释时,`inputChannel`必须引用已声明的 Bean。在这种情况下,
通道不会自动声明。| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| | |使用 Java 配置,你可以在`@Bean`方法级别上使用任何`@Conditional`(例如,`@Profile`)的定义来跳过 Bean 注册的某些条件原因。
下面的示例展示了如何这样做:

```
@Bean
@ServiceActivator(inputChannel = "skippedChannel")
@Profile("thing")
public MessageHandler skipped() {
return System.out::println;
}
```连同现有的容器逻辑一起,消息传递端点 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(); } ``` 你也可以将这些注释用作元注释。 #### 通知带注释的端点 见[使用注释为端点提供建议](./handler-advice.html#advising-with-annotations)。 ### 消息映射规则和约定 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); ``` 这个组合包含两个输入参数,其中一个参数的类型为`Map`。将非`Map`参数(无论顺序如何)映射到`Message`有效负载,并将`Map`或`Properties`(无论顺序如何)映射到消息头,从而为你提供了一种与`Message`结构进行交互的良好的 POJO 方式。 下面的示例不显示参数(无论返回的是什么): ``` public String doSomething(); ``` 此消息处理程序方法是基于发送到此处理程序所连接的输入通道的消息而调用的。但是没有映射`Message`数据,因此使`Message`充当事件或触发器来调用处理程序。根据规则[前面描述的](#message-mapping-rules)映射输出。 下面的示例不显示参数和 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,\)的方法参数和未注释的参数的任何方法签名都会导致模棱两可的情况并触发异常。| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 下一组示例都展示了导致歧义的多种方法。 具有多个方法的消息处理程序是基于前面(在示例中)描述的相同规则进行映射的。然而,有些情况看起来仍然令人困惑。 下面的示例展示了具有合法(可映射和明确的)签名的多个方法: ``` public class Something { public String doSomething(String str, Map m); public String doSomething(Map m); } ``` (无论这些方法的名称是相同的,还是不同的,都没有区别)。`Message`可以映射到任意一个方法。当消息有效负载可以映射到`str`并且消息头可以映射到`m`时,将调用第一个方法。第二种方法也可以通过仅将消息头映射到`m`而成为候选方法。更糟糕的是,这两种方法名称相同。首先,由于以下配置,这可能看起来很模糊: ``` ``` 它之所以有效,是因为映射首先基于有效负载,然后才是其他所有的东西。换句话说,第一个参数可以映射到有效负载的方法优先于所有其他方法。 现在考虑另一个例子,它产生了一个真正不明确的条件: ``` 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`属性来指定映射: ``` ``` 因为配置显式地映射了`doSomethingElse`方法,所以我们消除了歧义。