overview.md 38.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
# Spring 集成框架概述

##  Spring 集成概述

本章提供了 Spring 集成的核心概念和组件的高级介绍。它包括一些编程技巧,帮助你充分利用 Spring 集成。

### 背景

Spring 框架的关键主题之一是控制反转。从最广泛的意义上讲,这意味着框架代表在其上下文中管理的组件处理责任。这些组件本身被简化了,因为它们被免除了这些责任。例如,依赖注入解除了组件定位或创建它们的依赖关系的责任。类似地,面向方面的编程通过将业务组件模块化为可重用的方面,减轻了它们的泛型交叉关注点。在每种情况下,最终结果都是一个更易于测试、理解、维护和扩展的系统。

此外, Spring 框架和投资组合提供了用于构建 Enterprise 应用程序的全面的编程模型。开发人员受益于该模型的一致性,特别是它基于良好的最佳实践,例如对接口进行编程,以及更倾向于组合而不是继承。 Spring 的简化抽象和强大的支持库提高了开发人员的工作效率,同时提高了可测试性和可移植性。

Spring 整合的动机是这些相同的目标和原则。它将 Spring 编程模型扩展到消息传递领域,并建立在 Spring 现有的 Enterprise 集成支持的基础上,以提供更高级别的抽象。它支持消息驱动的体系结构,在这种体系结构中,控制反转应用于运行时关注的问题,例如某些业务逻辑何时应该运行以及响应应该在哪里发送。它支持消息的路由和转换,以便在不影响可测试性的情况下集成不同的传输和不同的数据格式。换句话说,消息传递和集成问题由框架处理。业务组件与基础架构进一步隔离,开发人员也不必承担复杂的集成责任。

作为 Spring 编程模型的扩展, Spring 集成提供了各种各样的配置选项,包括注释、支持名称空间的 XML、具有通用“ Bean”元素的 XML,以及直接使用底层 API。该 API 基于定义良好的策略接口和非侵入性的、授权的适配器。 Spring Integration 的设计灵感来自于认识到 Spring 内的常见模式与 Gregor Hohpe 和 Bobby Woolf(Addison Wesley,2004)在[*Enterprise 整合模式 *](https://www.enterpriseintegrationpatterns.com/)中描述的著名模式之间的强大亲和力。读过这本书的开发人员应该立即熟悉 Spring 集成概念和术语。

### 目标和原则

Spring 整合的动机是以下目标:

* 为实现复杂的 Enterprise 集成解决方案提供一个简单的模型。

* 在基于 Spring 的应用程序中,促进异步的、消息驱动的行为。

* 促进对现有 Spring 用户的直观的、渐进的采用。

Spring 一体化遵循以下原则:

* 为了模块化和可测试性,组件应该是松散耦合的。

* 框架应该强制分离业务逻辑和集成逻辑之间的关注点。

* 扩展点本质上应该是抽象的(但在定义良好的边界内),以促进重用和可移植性。

### 主要部件

从垂直的角度来看,分层的体系结构促进了关注点的分离,而层之间基于接口的契约促进了松耦合。 Spring-based 应用程序通常是以这种方式设计的,并且 Spring 框架和投资组合为遵循这种最佳实践的 Enterprise 应用程序的全栈提供了强大的基础。消息驱动的体系结构增加了一个水平视角,但这些相同的目标仍然是相关的。正如“分层架构”是一种非常通用和抽象的范式,消息传递系统通常遵循类似抽象的“管道和过滤器”模型。“过滤器”表示能够产生或消耗消息的任何组件,“管道”在过滤器之间传输消息,以使组件本身保持松散耦合。重要的是要注意,这两种高级范式并不相互排斥。支持“管道”的底层消息传递基础设施仍应封装在一个层中,该层的契约被定义为接口。类似地,“过滤器”本身应该在逻辑上位于应用程序服务层之上的一个层中进行管理,通过接口与这些服务进行交互,其方式与 Web 层的交互方式几乎相同。

#### 消息

在 Spring 集成中,消息是任何 Java 对象的通用包装器,它与框架在处理该对象时使用的元数据相结合。它由有效载荷和报头组成。有效负载可以是任何类型的,并且头包含通常需要的信息,例如 ID、时间戳、相关 ID 和返回地址。头也用于将值传递给连接的传输程序和从连接的传输程序中传递。例如,当从接收到的文件创建消息时,文件名可以存储在要由下游组件访问的头中。同样,如果消息的内容最终将由出站邮件适配器发送,那么各个属性(To、From、CC、Subject 和其他属性)可能会被上游组件配置为消息头值。开发人员还可以在头中存储任意的键值对。

茶陵後's avatar
茶陵後 已提交
43
![Message](https://docs.spring.io/spring-integration/docs/current/reference/html/images/message.jpg)
44

茶陵後's avatar
茶陵後 已提交
45
图1.信息
46 47 48 49 50

#### 消息通道

消息通道表示管道和过滤器体系结构的“管道”。生产者将消息发送到一个通道,消费者从一个通道接收消息。因此,消息通道使消息传递组件解耦,并且还为截获和监视消息提供了一个方便的点。

茶陵後's avatar
茶陵後 已提交
51
![消息通道](https://docs.spring.io/spring-integration/docs/current/reference/html/images/channel.jpg)
52

茶陵後's avatar
茶陵後 已提交
53
图2.消息通道
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

消息通道可以遵循点对点语义或发布订阅语义。对于点对点信道,发送到该信道的每条消息的接收者不能超过一个。另一方面,发布-订阅频道试图将每条消息广播给频道上的所有订阅者。 Spring 集成支持这两种模型。

虽然“点对点”和“发布-订阅”定义了两个选项,用于确定最终接收每条消息的消费者数量,但还有另一个重要的考虑因素:通道缓冲消息吗?在 Spring 集成中,可选通道能够缓冲队列中的消息。缓冲的优点是允许限制入站消息,从而防止使用者重载。然而,顾名思义,这也增加了一些复杂性,因为消费者只有在配置了 Poller 的情况下才能从这样的通道接收消息。另一方面,消费者连接到可订阅信道是简单的消息驱动的。[消息通道实现](./channel.html#channel-implementations)详细讨论了 Spring 集成中可用的各种信道实现方式。

#### 消息端点

Spring 集成的主要目标之一是通过控制反转来简化 Enterprise 集成解决方案的开发。这意味着你不必直接实现消费者和生产者,甚至不必在消息通道上构建消息和调用发送或接收操作。相反,你应该能够通过基于纯对象的实现来关注你的特定领域模型。然后,通过提供声明性配置,你可以将你的特定于域的代码“连接”到 Spring 集成提供的消息传递基础结构。负责这些连接的组件是消息端点。这并不意味着你必须直接连接现有的应用程序代码。任何现实世界中的 Enterprise 集成解决方案都需要一定数量的代码来关注集成问题,例如路由和转换。重要的是实现集成逻辑和业务逻辑之间的关注点分离。换句话说,与 Web 应用程序的 Model-View-Controller 范式一样,目标应该是提供一个薄而专用的层,将入站请求转换为服务层调用,然后将服务层返回值转换为出站响应。下一节概述了处理这些职责的消息端点类型,在接下来的章节中,你可以看到 Spring Integration 的声明式配置选项如何提供了一种非侵入性的方式来使用其中的每一种。

### 消息端点

消息端点表示管道和过滤器体系结构的“过滤器”。如前所述,端点的主要作用是将应用程序代码连接到消息传递框架,并以非侵入性的方式这样做。换句话说,理想情况下,应用程序代码应该不知道消息对象或消息通道。这类似于 MVC 范例中控制器的角色。就像控制器处理 HTTP 请求一样,消息端点处理消息。就像控制器映射到 URL 模式一样,消息端点也映射到消息通道。在这两种情况下,目标是相同的:将应用程序代码从基础架构中分离出来。在[*Enterprise 整合模式 *](https://www.enterpriseintegrationpatterns.com/)一书中详细讨论了这些概念和下面的所有模式。在这里,我们只提供了 Spring 集成支持的主要端点类型以及与这些类型相关的角色的高级描述。后面的章节详细介绍并提供了示例代码以及配置示例。

#### 消息转换器

消息转换器负责转换消息的内容或结构,并返回修改后的消息。可能最常见的转换器类型是将消息的有效负载从一种格式转换为另一种格式(例如从 XML 转换为`java.lang.String`)。类似地,Transformer 可以添加、删除或修改消息的标头值。

#### 消息过滤器

消息筛选器确定是否应该将消息传递到输出通道。这只需要一个布尔测试方法,该方法可以检查特定的有效负载内容类型、属性值、头的存在或其他条件。如果消息被接受,它将被发送到输出通道。如果不是,就会丢弃它(或者,对于更严格的实现,可能会抛出`Exception`)。消息过滤器通常与发布-订阅通道结合使用,在该通道中,多个消费者可能会收到相同的消息,并使用该过滤器的条件来缩小要处理的消息集。

|   |注意不要将管道和过滤器架构模式中“filter”的通用用法与这种特定的端点类型混淆,后者选择性地缩小了两个通道之间的消息流。<br/>管道和过滤器概念的“filter”与 Spring Integration 的消息端点:任何组件更接近可以连接到消息通道以发送或接收消息。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 消息路由器

消息路由器负责决定下一个应该接收消息的一个或多个通道(如果有的话)。通常,决策是基于消息的内容或消息头中可用的元数据。消息路由器通常被用作服务激活器或其他能够发送回复消息的端点上静态配置的输出通道的动态替代方案。同样,正如前面所描述的,消息路由器为多个订阅者使用的反应性消息过滤器提供了一种主动的替代方案。

茶陵後's avatar
茶陵後 已提交
82
![Router](https://docs.spring.io/spring-integration/docs/current/reference/html/images/router.jpg)
83

茶陵後's avatar
茶陵後 已提交
84
图3.消息路由器
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

#### splitter

拆分器是另一种类型的消息端点,其职责是接受来自其输入通道的消息,将该消息拆分成多个消息,并将每个消息发送到其输出通道。这通常用于将“复合”有效载荷对象划分为一组包含细分有效载荷的消息。

#### 聚合器

聚合器基本上是拆分器的镜像,是一种消息端点类型,它接收多个消息并将它们合并为一个消息。实际上,聚合器通常是包含拆分器的管道中的下游消费者。从技术上讲,聚合器比拆分器更复杂,因为它需要维护状态(要聚合的消息),决定完整的消息组何时可用,并在必要时超时。此外,在超时的情况下,聚合器需要知道是发送部分结果,丢弃它们,还是将它们发送到单独的通道。 Spring 集成提供了一个`CorrelationStrategy`、一个`ReleaseStrategy`,以及可配置的超时设置、是否在超时时时时发送部分结果,以及一个丢弃通道。

#### 服务激活器

服务激活器是用于将服务实例连接到消息传递系统的通用端点。必须配置输入消息通道,并且,如果要调用的服务方法能够返回值,则还可以提供输出消息通道。

|   |输出通道是可选的,因为每个消息还可以提供自己的“返回地址”头。<br/>同样的规则适用于所有消费者端点。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------|

服务激活器调用对某个服务对象的操作来处理请求消息,提取请求消息的有效负载并进行转换(如果方法不期望消息类型的参数)。每当服务对象的方法返回一个值时,如果有必要(如果它还不是消息类型),该返回值也同样会被转换为一个回复消息。该回复消息被发送到输出通道。如果没有配置输出通道,则将回复发送到消息的“返回地址”(如果可用)中指定的通道。

请求-应答服务激活端点将目标对象的方法连接到输入和输出消息通道。

茶陵後's avatar
茶陵後 已提交
105
![处理程序端点](https://docs.spring.io/spring-integration/docs/current/reference/html/images/handler-endpoint.jpg)
106

茶陵後's avatar
茶陵後 已提交
107
图4.服务激活器
108 109 110 111 112 113 114 115

|   |正如前面所讨论的,在[消息通道](#overview-components-channel)中,通道可以是可校对的或可下标的。<br/>在前面的图中,这是由“时钟”符号和实心箭头(轮询)和虚线箭头(订阅)来描述的。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 通道适配器

通道适配器是将消息通道连接到其他系统或传输的端点。通道适配器可以是入站的,也可以是出站的。通常,通道适配器在消息和从另一个系统接收或发送到另一个系统的任何对象或资源(文件、HTTP 请求、JMS 消息等)之间进行一些映射。根据传输方式的不同,通道适配器还可以填充或提取消息头值。 Spring 集成提供了许多通道适配器,这在接下来的章节中进行了描述。

茶陵後's avatar
茶陵後 已提交
116
![源端点](https://docs.spring.io/spring-integration/docs/current/reference/html/images/source-endpoint.jpg)
117

茶陵後's avatar
茶陵後 已提交
118
图5.入站通道适配器端点将源系统连接到`MessageChannel`
119 120 121 122

|   |消息源可以是可选的(例如,POP3)或消息驱动的(例如,IMAP IDLE)。<br/>在前面的图表中,这是由“时钟”符号、实心箭头和虚线箭头(消息驱动)来描述的。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

茶陵後's avatar
茶陵後 已提交
123
![目标端点](https://docs.spring.io/spring-integration/docs/current/reference/html/images/target-endpoint.jpg)
124

茶陵後's avatar
茶陵後 已提交
125
图6.出站通道适配器端点将`MessageChannel`连接到目标系统。
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338

|   |正如前面[消息通道](#overview-components-channel)中所讨论的,通道可以是可选的或可下标的。<br/>在前面的图表中,这是由“时钟”符号和实心箭头(轮询)和虚线箭头(订阅)来描述的。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 端点 Bean Names

消费端点(任何带有`inputChannel`的端点)由两个 bean 组成,一个是消费者,另一个是消息处理程序。使用者拥有对消息处理程序的引用,并在消息到达时调用它。

考虑以下 XML 示例:

```
<int:service-activator id = "someService" ... />
```

鉴于上述示例, Bean 名称如下:

* 消费者:`someService`(the`id`

* 处理者:`someService.handler`

当使用 Enterprise 集成模式注释时,名称取决于几个因素。考虑以下带注释的 POJO 示例:

```
@Component
public class SomeComponent {

    @ServiceActivator(inputChannel = ...)
    public String someMethod(...) {
        ...
    }

}
```

举个例子, Bean 名称如下:

* 消费者:`someComponent.someMethod.serviceActivator`

* 处理者:`someComponent.someMethod.serviceActivator.handler`

从版本 5.0.4 开始,你可以使用`@EndpointId`注释来修改这些名称,如下例所示:

```
@Component
public class SomeComponent {

    @EndpointId("someService")
    @ServiceActivator(inputChannel = ...)
    public String someMethod(...) {
        ...
    }

}
```

鉴于上述例子, Bean 名称如下:

* 消费者:`someService`

* 处理程序:`someService.handler`

`@EndpointId`使用 XML 配置创建由`id`属性创建的名称。考虑以下注释 Bean 的示例:

```
@Configuration
public class SomeConfiguration {

    @Bean
    @ServiceActivator(inputChannel = ...)
    public MessageHandler someHandler() {
        ...
    }

}
```

举个例子, Bean 名称如下:

* 消费者:`someConfiguration.someHandler.serviceActivator`

* 处理程序:`someHandler``@Bean`名称)

从版本 5.0.4 开始,你可以使用`@EndpointId`注释来修改这些名称,如下例所示:

```
@Configuration
public class SomeConfiguration {

    @Bean("someService.handler")             (1)
    @EndpointId("someService")               (2)
    @ServiceActivator(inputChannel = ...)
    public MessageHandler someHandler() {
        ...
    }

}
```

|**1**|处理程序:`someService.handler`( Bean 名称)|
|-----|----------------------------------------------|
|**2**|使用者:`someService`(端点 ID)|

只要使用将`.handler`附加到`@Bean`名称的约定,`@EndpointId`注释就会创建由`id`属性通过 XML 配置创建的名称。

有一种特殊情况是创建了第三个 Bean:出于体系结构的原因,如果`MessageHandler``@Bean`不定义`AbstractReplyProducingMessageHandler`,则框架将所提供的 Bean 包装在`ReplyProducingMessageHandlerWrapper`中。此包装器支持请求处理程序的建议处理,并发出正常的“产生无回复”调试日志消息。它的 Bean 名称是处理程序 Bean 名称加上`.wrapper`(当存在`@EndpointId`时——否则,它是正常生成的处理程序名称)。

类似地[可选消息源](./polling-consumer.html#pollable-message-source)创建两个 bean,一个`SourcePollingChannelAdapter`和一个`MessageSource`

考虑以下 XML 配置:

```
<int:inbound-channel-adapter id = "someAdapter" ... />
```

考虑到前面的 XML 配置, Bean 名称如下:

* SPCA:`someAdapter`(the`id`

* 处理者:`someAdapter.source`

考虑以下 POJO 的 Java 配置,以定义`@EndpointId`:

```
@EndpointId("someAdapter")
@InboundChannelAdapter(channel = "channel3", poller = @Poller(fixedDelay = "5000"))
public String pojoSource() {
    ...
}
```

给出了前面的 Java 配置示例, Bean 名称如下:

* SPCA:`someAdapter`

* 处理者:`someAdapter.source`

考虑 Bean 的以下 Java 配置来定义`@EndpointID`:

```
@Bean("someAdapter.source")
@EndpointId("someAdapter")
@InboundChannelAdapter(channel = "channel3", poller = @Poller(fixedDelay = "5000"))
public MessageSource<?> source() {
    return () -> {
        ...
    };
}
```

鉴于上述例子, Bean 名称如下:

* SPCA:`someAdapter`

* 处理程序:`someAdapter.source`(只要你使用将`.source`附加到`@Bean`名称的约定)

### 配置和`@EnableIntegration`

在本文档中,你可以看到对 XML 命名空间支持的引用,该支持用于在 Spring 集成流中声明元素。这种支持由一系列命名空间解析器提供,这些解析器生成适当的 Bean 定义来实现特定组件。例如,许多端点由`MessageHandler` Bean 和`ConsumerEndpointFactoryBean`组成,其中注入了处理程序和输入通道名。

Spring 第一次遇到集成名称空间元素时,框架会自动声明用于支持运行时环境的许多 bean(任务调度器、隐式通道创建器和其他)。

|   |4.0 版本引入了`@EnableIntegration`注释,为了允许 Spring 集成基础设施 bean 的注册(请参见[Javadoc](https://docs.spring.io/spring-integration/docs/latest-ga/api/org/springframework/integration/config/EnableIntegration.html))。<br/>当只使用 Java 配置时,这个注释是必需的——例如在 Spring Boot 或 Spring Integration Messaging 注释支持和 Spring Integration Java DSL 中不使用 XML 集成配置。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

当你有一个没有 Spring 集成组件的父上下文和两个或更多使用 Spring 集成的子上下文时,`@EnableIntegration`注释也很有用。它允许在父上下文中只声明这些公共组件一次。

`@EnableIntegration`注释在应用程序上下文中注册了许多基础设施组件。特别是,它:

* 注册一些内置的 bean,例如`errorChannel`及其`LoggingHandler`,对于 pollers 是`taskScheduler``jsonPath`spel-function,等等。

* 添加几个`BeanFactoryPostProcessor`实例,以增强用于全局和默认集成环境的`BeanFactory`

* 添加几个`BeanPostProcessor`实例,以增强或转换和包装特定的 bean 以用于集成目的。

* 添加注释处理器来解析消息传递注释,并在应用程序上下文中为它们注册组件。

`@IntegrationComponentScan`注释还允许 Classpath 扫描。该注释与标准 Spring 框架`@ComponentScan`注释起着类似的作用,但它仅限于 Spring 集成所特有的组件和注释,这是标准 Spring 框架组件扫描机制无法达到的。有关示例,请参见[`@MessagingGateway`注释]。

`@EnablePublisher`注释注册了`PublisherAnnotationBeanPostProcessor` Bean,并为那些`@Publisher`注释配置了`default-publisher-channel`,这些注释是在没有`channel`属性的情况下提供的。如果找到了一个以上的`@EnablePublisher`注释,则对于默认通道,它们都必须具有相同的值。有关更多信息,请参见[带有`@Publisher`注释的注释驱动配置]。

引入了`@GlobalChannelInterceptor`注释来标记用于全局信道拦截的`ChannelInterceptor`bean。该注释类似于`<int:channel-interceptor>`XML 元素(参见[全局信道拦截器配置](./channel.html#global-channel-configuration-interceptors))。`@GlobalChannelInterceptor`注释可以放在类级别(带有`@Component`原型注释)或`@Bean`中的`@Configuration`类中的方法上。在这两种情况下, Bean 都必须实现`ChannelInterceptor`

从版本 5.1 开始,全局信道拦截器应用于动态注册的信道——例如,当使用 Java DSL 时,通过使用`beanFactory.initializeBean()`初始化的 bean 或通过`IntegrationFlowContext`初始化的 bean。以前,在刷新应用程序上下文后创建 bean 时,不会应用拦截器。

`@IntegrationConverter`注释标记`Converter``GenericConverter`,或`ConverterFactory`bean 作为`integrationConversionService`的候选转换器。这个注释类似于`<int:converter>`XML 元素(参见[有效载荷类型转换](./endpoint.html#payload-type-conversion))。你可以将`@IntegrationConverter`注释放置在类级(带有`@Component`原型注释),或者将`@Bean`方法放置在`@Configuration`类中。

有关消息传递注释的更多信息,请参见[注释支持](./configuration.html#annotations)

### 编程注意事项

只要有可能,就应该使用纯旧的 Java 对象,并且只在绝对必要的情况下在代码中公开框架。有关更多信息,请参见[POJO 方法调用](#pojo-invocation)

如果确实将框架公开给类,那么需要考虑一些因素,尤其是在应用程序启动期间:

* 如果你的组件是`ApplicationContextAware`,则通常不应在`setApplicationContext()`方法中使用`ApplicationContext`。相反,存储一个引用,并将这种使用推迟到上下文生命周期的稍后阶段。

* 如果你的组件是`InitializingBean`或使用`@PostConstruct`方法,请不要从这些初始化方法发送任何消息。调用这些方法时,应用程序上下文尚未初始化,发送这样的消息很可能会失败。如果在启动期间需要发送消息,请实现`ApplicationListener`并等待`ContextRefreshedEvent`。或者,实现`SmartLifecycle`,将你的 Bean 置于后期阶段,并从`start()`方法发送消息。

#### 使用打包(例如,阴影)JAR 时的注意事项

Spring 通过使用 Spring 框架的`SpringFactories`机制来加载几个`IntegrationConfigurationInitializer`类,集成引导了某些特性。这包括`-core`JAR 以及某些其他 JAR,包括`-http``-jmx`。此进程的信息存储在每个 JAR 中的`META-INF/spring.factories`文件中。

一些开发人员更喜欢使用著名的工具,例如[Apache Maven Shade Plugin](https://maven.apache.org/plugins/maven-shade-plugin/),将其应用程序和所有依赖项重新打包到一个 JAR 中。

默认情况下,在生成带阴影的 JAR 时,shade 插件不合并`spring.factories`文件。

除了`spring.factories`之外,其他`META-INF`文件(`spring.handlers``spring.schemas`)也用于 XML 配置。这些文件也需要合并。

|   |[Spring Boot’s executable jar mechanism](https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html)采用了一种不同的方法,它嵌套了 JAR,从而保留了类路径上的每个`spring.factories`文件。<br/>因此,对于 Spring 引导应用程序,如果使用其默认的可执行 JAR 格式,就不需要更多了。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

即使不使用 Spring boot,也可以使用 boot 提供的工具,通过为上述文件添加 Transformers 来增强 shade 插件。下面的示例展示了如何配置插件:

茶陵後's avatar
茶陵後 已提交
339
例1. POM.xml
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555

```
...
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <configuration>
                <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
                <createDependencyReducedPom>true</createDependencyReducedPom>
            </configuration>
            <dependencies>
                <dependency> (1)
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring.boot.version}</version>
                </dependency>
            </dependencies>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <transformers> (2)
                            <transformer
                                implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                <resource>META-INF/spring.handlers</resource>
                            </transformer>
                            <transformer
                                implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                                <resource>META-INF/spring.factories</resource>
                            </transformer>
                            <transformer
                                implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                <resource>META-INF/spring.schemas</resource>
                            </transformer>
                            <transformer
                                implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
                        </transformers>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
...
```

具体来说,

|**1**|添加`spring-boot-maven-plugin`作为依赖项。|
|-----|---------------------------------------------------|
|**2**|配置变压器。|

你可以为`${spring.boot.version}`添加一个属性,或者使用显式版本。

### 编程技巧

本节记录了从 Spring 集成中获得最大收益的一些方法。

#### XML 模式

在使用 XML 配置时,为了避免出现错误的模式验证错误,你应该使用一个“ Spring-aware”IDE,例如 Spring Tool Suite、带有 Spring IDE 插件的 Eclipse 或 IntelliJ IDEA。这些 IDE 知道如何从 Classpath 解析正确的 XML 模式(通过使用 JARS 中的`META-INF/spring.schemas`文件)。当在插件中使用 STS 或 Eclipse 时,你必须在项目上启用`Spring Project Nature`

由于兼容性的原因,托管在 Internet 上的某些遗留模块(在版本 1.0 中存在的模块)的模式是 1.0 版本。如果你的 IDE 使用了这些模式,那么你很可能会看到错误。

这些在线模式中的每一个都有一个类似于以下的警告:

|   |该模式适用于 Spring 集成核心的 1.0 版本,<br/>我们不能将其更新为当前模式,因为这将中断任何使用 1.0.3 或更低版本的应用程序,<br/>适用于后续版本,无版本模式从 Classpath 解析并从 JAR 获得。<br/>请参阅 Github:<br/><br/>[https://github.com/spring-projects/spring-integration/tree/main/spring-integration-core/src/main/resources/org/springframework/integration/config](https://github.com/spring-projects/spring-integration/tree/main/spring-integration-core/src/main/resources/org/springframework/integration/config)|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

受影响的模块是

* `core`(`spring-integration.xsd`)

* `file`

* `http`

* `jms`

* `mail`

* `rmi`

* `security`

* `stream`

* `ws`

* `xml`

#### 查找 Java 和 DSL 配置的类名

通过 XML 配置和 Spring 集成名称空间支持,XML 解析器隐藏了目标 bean 的声明和连接方式。对于 Java 配置,理解目标终端用户应用程序的框架 API 非常重要。

EIP 实现的第一类公民是`Message``Channel``Endpoint`(参见本章前面的[主要部件](#overview-components))。它们的实现(契约)是:

* `org.springframework.messaging.Message`:见[Message](./message.html#message)

* `org.springframework.messaging.MessageChannel`:见[消息通道](./channel.html#channel)

* `org.springframework.integration.endpoint.AbstractEndpoint`:见[Poller](./polling-consumer.html#polling-consumer)

前两个非常简单,可以理解如何实现、配置和使用。最后一个值得更多关注。

`AbstractEndpoint`在整个 Spring 框架中被广泛用于不同的组件实现。它的主要实现方式是:

* `EventDrivenConsumer`,用于订阅`SubscribableChannel`以侦听消息。

* `PollingConsumer`,用于轮询来自`PollableChannel`的消息。

当你使用消息传递注释或 Java DSL 时,你不需要担心这些组件,因为框架会自动用适当的注释和`BeanPostProcessor`实现来生成它们。当手动构建组件时,你应该使用`ConsumerEndpointFactoryBean`来帮助确定要创建的目标`AbstractEndpoint`消费者实现,基于所提供的`inputChannel`属性。

另一方面,`ConsumerEndpointFactoryBean`在框架-`org.springframework.messaging.MessageHandler`中委托给另一个一等公民。该接口实现的目标是处理来自通道的端点所消耗的消息。 Spring 集成中的所有 EIP 组件都是`MessageHandler`实现(例如,`AggregatingMessageHandler``MessageTransformingHandler``AbstractMessageSplitter`,以及其他)。目标协议出站适配器(`FileWritingMessageHandler``HttpRequestExecutingMessageHandler``AbstractMqttMessageHandler`等)也是`MessageHandler`的实现方式。在使用 Java 配置开发 Spring 集成应用程序时,应该查看 Spring 集成模块,以找到用于`MessageHandler`配置的适当的`@ServiceActivator`实现。例如,要发送 XMPP 消息(参见[XMPP 支持](./xmpp.html#xmpp)),你应该配置如下内容:

```
@Bean
@ServiceActivator(inputChannel = "input")
public MessageHandler sendChatMessageHandler(XMPPConnection xmppConnection) {
    ChatMessageSendingMessageHandler handler = new ChatMessageSendingMessageHandler(xmppConnection);

    DefaultXmppHeaderMapper xmppHeaderMapper = new DefaultXmppHeaderMapper();
    xmppHeaderMapper.setRequestHeaderNames("*");
    handler.setHeaderMapper(xmppHeaderMapper);

    return handler;
}
```

`MessageHandler`实现表示消息流的出站和处理部分。

入站消息流端有自己的组件,这些组件分为轮询和监听行为。监听(消息驱动)组件非常简单,通常只需要一个目标类实现就可以准备好生成消息。监听组件可以是单向`MessageProducerSupport`实现,(例如`AbstractMqttMessageDrivenChannelAdapter``ImapIdleChannelAdapter`)或请求-回复`MessagingGatewaySupport`实现(例如`AmqpInboundGateway``AbstractWebServiceInboundGateway`)。

轮询入站端点用于那些不提供侦听器 API 或不打算用于这种行为的协议,包括任何基于文件的协议(例如 FTP),任何数据库(RDBMS 或 NoSQL),以及其他协议。

这些入站端点由两个组件组成:Poller 配置,用于周期性地启动轮询任务,以及消息源类,用于从目标协议读取数据并为下游集成流生成消息。Poller 配置的第一个类是`SourcePollingChannelAdapter`。这是另一个`AbstractEndpoint`实现,但特别是用于轮询以启动集成流。通常,对于消息传递注释或 Java DSL,你不应该担心这个类。该框架基于`@InboundChannelAdapter`配置或 Java DSL Builder 规范为其生成 Bean。

消息源组件对于目标应用程序开发来说更加重要,它们都实现了`MessageSource`接口(例如,`MongoDbMessageSource``AbstractTwitterMessageSource`)。考虑到这一点,使用 JDBC 从 RDBMS 表中读取数据的配置可以类似于以下内容:

```
@Bean
@InboundChannelAdapter(value = "fooChannel", poller = @Poller(fixedDelay="5000"))
public MessageSource<?> storedProc(DataSource dataSource) {
    return new JdbcPollingChannelAdapter(dataSource, "SELECT * FROM foo where status = 0");
}
```

你可以在特定的 Spring 集成模块(在大多数情况下,在相应的包中)中找到目标协议所需的所有入站和出站类。例如,`spring-integration-websocket`适配器是:

* `o.s.i.websocket.inbound.WebSocketInboundChannelAdapter`:实现`MessageProducerSupport`来侦听套接字上的帧并生成发送给通道的消息。

* `o.s.i.websocket.outbound.WebSocketOutboundMessageHandler`:单向`AbstractMessageHandler`实现,以将传入消息转换为适当的帧并通过 WebSocket 发送。

如果你熟悉 Spring Integration XML 配置(从版本 4.3 开始),我们将在 XSD 元素定义中提供有关哪些目标类用于为适配器或网关声明 bean 的信息,如下例所示:

```
<xsd:element name="outbound-async-gateway">
    <xsd:annotation>
		<xsd:documentation>
Configures a Consumer Endpoint for the 'o.s.i.amqp.outbound.AsyncAmqpOutboundGateway'
that will publish an AMQP Message to the provided Exchange and expect a reply Message.
The sending thread returns immediately; the reply is sent asynchronously; uses 'AsyncRabbitTemplate.sendAndReceive()'.
       </xsd:documentation>
	</xsd:annotation>
```

### POJO 方法调用

正如[程序设计方面的考虑因素](#programming-considerations)中所讨论的,我们建议使用 POJO 编程风格,如下例所示:

```
@ServiceActivator
public String myService(String payload) { ... }
```

在这种情况下,框架提取`String`有效负载,调用你的方法,并将结果包装成消息,以发送到流中的下一个组件(原始标题被复制到新消息)。实际上,如果使用 XML 配置,你甚至不需要`@ServiceActivator`注释,正如下面的配对示例所示:

```
<int:service-activator ... ref="myPojo" method="myService" />
```

```
public String myService(String payload) { ... }
```

只要类上的公共方法没有歧义,就可以省略`method`属性。

你还可以在 POJO 方法中获得头信息,如下例所示:

```
@ServiceActivator
public String myService(@Payload String payload, @Header("foo") String fooHeader) { ... }
```

你还可以取消消息中的属性引用,如下例所示:

```
@ServiceActivator
public String myService(@Payload("payload.foo") String foo, @Header("bar.baz") String barbaz) { ... }
```

因为各种 POJO 方法调用是可用的,所以 5.0 之前的版本使用 SPEL( Spring 表达式语言)来调用 POJO 方法。与通常在方法中完成的实际工作相比,对于这些操作,SPEL(甚至解释)通常“足够快”。然而,从版本 5.0 开始,只要有可能,默认情况下都会使用`org.springframework.messaging.handler.invocation.InvocableHandlerMethod`。该技术通常比解释的 SPEL 执行得更快,并且与其他 Spring 消息传递项目一致。`InvocableHandlerMethod`类似于 Spring MVC 中用于调用控制器方法的技术。在使用 SPEL 时,仍然会调用某些方法。如前面所讨论的,示例包括带注释的参数,这些参数具有可撤销的属性。这是因为 SPEL 具有导航属性路径的能力。

可能还有一些我们尚未考虑的其他角情况也不适用于`InvocableHandlerMethod`实例。因此,在这些情况下,我们会自动退回到使用 SPEL。

如果你愿意,还可以设置 POJO 方法,使其始终使用 SPEL,并使用`UseSpelInvoker`注释,如下例所示:

```
@UseSpelInvoker(compilerMode = "IMMEDIATE")
public void bar(String bar) { ... }
```

如果省略`compilerMode`属性,则`spring.expression.compiler.mode`系统属性决定编译器模式。有关编译后的 SPEL 的更多信息,请参见[SPEL 编译](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#expressions-spel-compilation)