# 信息

### 消息

Spring Integration`Message`是一个通用的数据容器。任何对象都可以作为有效负载提供，并且每个`Message`实例都包括包含用户可扩展属性的头作为键-值对。

#### `Message`接口

下面的清单显示了`Message`接口的定义：

```
public interface Message<T> {

    T getPayload();

    MessageHeaders getHeaders();

}
```

`Message`接口是 API 的核心部分。通过将数据封装在通用包装器中，消息传递系统可以在不了解数据类型的情况下传递数据。随着应用程序逐渐支持新类型，或者当类型本身被修改或扩展时，消息传递系统不会受到影响。另一方面，当消息传递系统中的某些组件确实需要访问关于`Message`的信息时，这样的元数据通常可以存储到并从消息头中的元数据检索。

#### 消息头

正如 Spring 集成允许任何`Object`被用作`Message`的有效负载一样，它也支持任何`Object`类型作为头值。实际上，`MessageHeaders`类实现了`java.util.Map_ interface`，如下面的类定义所示：

```
public final class MessageHeaders implements Map<String, Object>, Serializable {
  ...
}
```

|   |尽管`MessageHeaders`类实现了`Map`，但它实际上是一个只读实现。<br/>任何试图`put`映射中的值都会导致`UnsupportedOperationException`。<br/>同样适用于`remove`和`clear`。<br/>，因为消息可能传递给多个消费者，`Map`的结构不能修改。<br/>同样，消息的有效负载`Object`在初始创建之后也不能`set`。<br/>但是，标头值本身（或有效负载对象）的可变性是有意留给框架用户的决定。|
|---| |

作为`Map`的一种实现，可以通过调用`get(..)`的标题来检索标题。或者，你可以提供预期的`Class`作为附加参数。更好的是，当检索一个预定义的值时，可以使用方便的 getter。下面的示例展示了这三个选项中的每一个：

```
Object someValue = message.getHeaders().get("someKey");

CustomerId customerId = message.getHeaders().get("customerId", CustomerId.class);

Long timestamp = message.getHeaders().getTimestamp();
```

下表描述了预定义的消息头：

|                   Header Name                    |                            Header Type                             |用法|
|--------------------------------------------------|--------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|        ```<br/> MessageHeaders.ID<br/>```        |                  ```<br/> java.util.UUID<br/>```                   |此消息实例的标识符。<br/>每次消息发生突变时都会更改。|
|  ```<br/> MessageHeaders.<br/>TIMESTAMP<br/>```  |                  ```<br/> java.lang.Long<br/>```                   |消息创建的时间。<br/>每次消息发生突变时都会更改。|
|```<br/> MessageHeaders.<br/>REPLY_CHANNEL<br/>```|```<br/> java.lang.Object<br/>(String or<br/>MessageChannel)<br/>```|当没有配置显式输出通道且没有`ROUTING_SLIP`或`ROUTING_SLIP`已耗尽时，将向其发送回复（如果有的话）的通道。<br/>如果该值是`String`，则它必须表示 Bean 名称或已由`ChannelRegistry.`生成|
|```<br/> MessageHeaders.<br/>ERROR_CHANNEL<br/>```|```<br/> java.lang.Object<br/>(String or<br/>MessageChannel)<br/>```|发送错误到的通道。<br/>如果该值是`String`，则它必须表示 Bean 名称或已由`ChannelRegistry.`生成|

许多入站和出站适配器实现还提供或期望某些标头，并且你可以配置其他用户定义的标头。这些标头的常量可以在存在这样的标头的模块中找到——例如`AmqpHeaders`，`JmsHeaders`，等等。

##### `MessageHeaderAccessor` API

从 Spring Framework4.0 和 Spring Integration4.0 开始，核心消息抽象已移至`spring-messaging`模块，并引入了`MessageHeaderAccessor`API，以在消息实现上提供额外的抽象。现在，在`IntegrationMessageHeaderAccessor`类中声明了所有（核心） Spring 特定于集成的消息头常量。下表描述了预定义的消息头：

|                                 Header Name                                  |                          Header Type                          |用法|
|------------------------------------------------------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|    ```<br/> IntegrationMessageHeaderAccessor.<br/>CORRELATION_ID<br/>```     |               ```<br/> java.lang.Object<br/>```               |用于将两条或多条消息关联起来。|
|    ```<br/> IntegrationMessageHeaderAccessor.<br/>SEQUENCE_NUMBER<br/>```    |              ```<br/> java.lang.Integer<br/>```               |通常是一个序列号，带有一组带有`SEQUENCE_SIZE`的消息，但也可以在`<resequencer/>`中用于对一组无界的消息进行重新排序。|
|     ```<br/> IntegrationMessageHeaderAccessor.<br/>SEQUENCE_SIZE<br/>```     |              ```<br/> java.lang.Integer<br/>```               |一组相关消息中的消息数量。|
|    ```<br/> IntegrationMessageHeaderAccessor.<br/>EXPIRATION_DATE<br/>```    |                ```<br/> java.lang.Long<br/>```                |指示消息何时过期。<br/>框架不直接使用，但可以用一个表头 enricher 设置，并在配置为`UnexpiredMessageSelector`的`<filter/>`中使用。|
|       ```<br/> IntegrationMessageHeaderAccessor.<br/>PRIORITY<br/>```        |              ```<br/> java.lang.Integer<br/>```               |消息优先级——例如，在`PriorityChannel`内。|
|   ```<br/> IntegrationMessageHeaderAccessor.<br/>DUPLICATE_MESSAGE<br/>```   |              ```<br/> java.lang.Boolean<br/>```               |如果一条消息被幂等接收器拦截器检测为重复消息，则为 true。<br/>参见[幂等接收机 Enterprise 集成模式](./handler-advice.html#idempotent-receiver)。|
|  ```<br/> IntegrationMessageHeaderAccessor.<br/>CLOSEABLE_RESOURCE<br/>```   |              ```<br/> java.io.Closeable<br/>```               |如果消息与`Closeable`相关联，则存在此消息头。<br/>一个示例是与使用 FTP、SFTP 等进行的流文件传输相关联的`Session`。|
|   ```<br/> IntegrationMessageHeaderAccessor.<br/>DELIVERY_ATTEMPT<br/>```    |         ```<br/> java.lang.<br/>AtomicInteger<br/>```         |如果消息驱动通道适配器支持`RetryTemplate`的配置，则此头包含当前的传递尝试。|
|```<br/> IntegrationMessageHeaderAccessor.<br/>ACKNOWLEDGMENT_CALLBACK<br/>```|```<br/> o.s.i.support.<br/>Acknowledgment<br/>Callback<br/>```|如果入站端点支持它，则回调以接受、拒绝或请求消息。<br/>参见[延迟确认可收集消息源](./polling-consumer.html#deferred-acks-message-source)和[MQTT 手册 ACKS ](./mqtt.html#mqtt-ack-mode)。|

如下例所示，在`IntegrationMessageHeaderAccessor`类中提供了一些标题的方便的类型化 getter:

```
IntegrationMessageHeaderAccessor accessor = new IntegrationMessageHeaderAccessor(message);
int sequenceNumber = accessor.getSequenceNumber();
Object correlationId = accessor.getCorrelationId();
...
```

下表描述了也出现在`IntegrationMessageHeaderAccessor`中但通常不被用户代码使用的标题（即，它们通常被 Spring Integration 的内部部分使用——此处包含它们是为了完整起见）：

|                              Header Name                              |                       Header Type                        |用法|
|-----------------------------------------------------------------------|----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
|```<br/> IntegrationMessageHeaderAccessor.<br/>SEQUENCE_DETAILS<br/>```|    ```<br/> java.util.<br/>List<List<Object>><br/>```    |当需要嵌套相关性时（例如，`splitter→…​→splitter→…​→aggregator→…​→aggregator`）使用的相关数据堆栈。|
|  ```<br/> IntegrationMessageHeaderAccessor.<br/>ROUTING_SLIP<br/>```  |```<br/> java.util.<br/>Map<List<Object>, Integer><br/>```|见[布线滑移](./router.html#routing-slip)。|

##### 消息 ID 生成

当一条消息在应用程序中转换时，每当它发生突变（例如，通过转换器）时，都会分配一个新的消息 ID。消息 ID 是`UUID`。从 Spring Integration3.0 开始，用于 IS Generation 的默认策略比以前的`java.util.UUID.randomUUID()`实现更有效。它使用基于安全随机种子的简单随机数，而不是每次创建一个安全随机数。

可以通过声明在应用程序上下文中实现`org.springframework.util.IdGenerator`的 Bean 来选择不同的 UUID 生成策略。

|   |在类装入器中只能使用一个 UUID 生成策略，<br/>这意味着，如果两个或更多的应用程序上下文在同一个类装入器中运行，那么它们共享相同的策略，<br/>如果其中一个上下文更改了策略，它被所有上下文使用。<br/>如果同一个类装入器中的两个或更多个上下文声明类型`org.springframework.util.IdGenerator`的 Bean，则它们必须都是同一个类的实例。<br/>否则，试图替换自定义策略的上下文将无法初始化。<br/>如果策略相同，但是参数化后，在第一个要初始化的上下文中使用了策略。|
|---| |

除了默认的策略之外，还提供了两个额外的`IdGenerators`。`org.springframework.util.JdkIdGenerator`使用了以前的`UUID.randomUUID()`机制。当不真正需要 UUID 并且简单的递增值就足够了时，可以使用`o.s.i.support.IdGenerators.SimpleIncrementingIdGenerator`。

##### 只读标题

`MessageHeaders.ID`和`MessageHeaders.TIMESTAMP`是只读标头，不能重写。

从版本 4.3.2 开始，`MessageBuilder`提供了`readOnlyHeaders(String…​ readOnlyHeaders)`API 来定制不应该从上游`Message`复制的标题列表。默认情况下只读`MessageHeaders.ID`和`MessageHeaders.TIMESTAMP`。提供了全局`spring.integration.readOnly.headers`属性（参见[全局属性](./configuration.html#global-properties)）来为框架组件定制`DefaultMessageBuilderFactory`。当你不想填充一些开箱即用的标题时，这可能会很有用，例如`contentType`由`ObjectToJsonTransformer`（参见[JSON 变形金刚](./transformer.html#json-transformers)）。

当尝试使用`MessageBuilder`构建新消息时，将忽略这种头，并将特定的`INFO`消息发送到日志中。

从版本 5.0 开始，[消息传递网关](./gateway.html#gateway)，[页眉 Enricher ](./content-enrichment.html#header-enricher)，[内容更丰富](./content-enrichment.html#payload-enricher)和[头过滤器](./transformer.html#header-filter)不允许你配置`MessageHeaders.ID`和`MessageHeaders.TIMESTAMP`使用`DefaultMessageBuilderFactory`时的头名称，并抛出`BeanInitializationException`。

##### 报头传播

当消息产生端点（例如[服务激活器](./service-activator.html#service-activator)）处理（和修改）消息时，通常将入站头传播到出站消息。一个例外是[变压器](./transformer.html#transformer)，当完整的消息返回到框架时。在这种情况下，用户代码负责整个出站消息。当转换器只返回有效负载时，入站报头将被传播。此外，只有在出站消息中不存在头文件的情况下，才会对其进行传播，从而允许你根据需要更改头文件的值。

从版本 4.3.10 开始，你可以配置消息处理程序（用于修改消息并产生输出）以抑制特定头部的传播。要配置不希望被复制的标题，请在`MessageProducingMessageHandler`抽象类上调用`setNotPropagatedHeaders()`或`addNotPropagatedHeaders()`方法。

还可以通过将`META-INF/spring.integration.properties`中的`readOnlyHeaders`属性设置为以逗号分隔的标题列表，来全局抑制特定消息标题的传播。

从版本 5.0 开始，`setNotPropagatedHeaders()`上的`AbstractMessageProducingHandler`实现将应用简单的模式（`xxx*`，`**xxx**`**，`*xxx`**，或`xxx*yyy`），以允许带有公共后缀或前缀的过滤头。有关更多信息，请参见[`PatternMatchUtils`Javadoc]（https://DOCS. Spring.io/ Spring-integration/api/org/springframework/integration/util/patternmatchutils.html）。当其中一个模式`*`（星号）时，不传播头。所有其他模式都被忽略了。在这种情况下，服务激活器的行为与转换器相同，任何所需的头都必须在从服务方法返回的`Message`中提供。`notPropagatedHeaders()`选项在`ConsumerEndpointSpec`中可用于 Java DSL，也可用于`<service-activator>`组件的 XML 配置，作为`not-propagated-headers`属性。

|   |头传播抑制不适用于那些不修改消息的端点，例如[bridges](./bridge.html#bridge)和[routers](./router.html#router)。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 消息实现

`Message`接口的基本实现是`GenericMessage<T>`，它提供了两个构造函数，如以下清单所示：

```
new GenericMessage<T>(T payload);

new GenericMessage<T>(T payload, Map<String, Object> headers)
```

当创建`Message`时，将生成一个随机唯一 ID。接受`Map`标题的构造函数将提供的标题复制到新创建的`Message`。

还有一种方便的`Message`实现方式被设计用于通信错误条件。该实现将`Throwable`对象作为其有效负载，如下例所示：

```
ErrorMessage message = new ErrorMessage(someThrowable);

Throwable t = message.getPayload();
```

注意，这个实现利用了`GenericMessage`基类是参数化的这一事实。因此，如两个示例中所示，在检索`Message`有效载荷`Object`时，无需强制转换。

#### the`MessageBuilder`助手类

你可能注意到`Message`接口为其有效负载和标题定义了检索方法，但没有提供 setter。其原因是`Message`在其初始创建后不能进行修改。因此，当`Message`实例被发送到多个消费者（例如，通过发布-订阅通道）时，如果其中一个消费者需要发送具有不同有效负载类型的答复，它必须创建一个新的`Message`。因此，其他消费者不受这些变化的影响。请记住，多个消费者可能会访问相同的有效负载实例或标头值，而这样的实例本身是否不可变是一个留给你的决定。换句话说，`Message`实例的契约类似于不可修改的`Collection`实例的契约，`MessageHeaders`映射进一步证明了这一点。即使`MessageHeaders`类实现了`java.util.Map`，在`put`实例上调用`put`操作（或“remove”或“clear”）的任何尝试都会导致`UnsupportedOperationException`实例。

Spring 集成并不需要将映射的创建和填充传递到 GenericMessage 构造函数，而是提供了一种构造消息的方便得多的方法：`MessageBuilder`。`MessageBuilder`提供了两个工厂方法，用于从现有的`Message`实例或使用有效负载`Object`创建`Message`实例。当从现有的`Message`构建时，该`Message`的头和有效负载被复制到新的`Message`，如下例所示：

```
Message<String> message1 = MessageBuilder.withPayload("test")
        .setHeader("foo", "bar")
        .build();

Message<String> message2 = MessageBuilder.fromMessage(message1).build();

assertEquals("test", message2.getPayload());
assertEquals("bar", message2.getHeaders().get("foo"));
```

如果你需要使用新的有效负载创建`Message`，但仍然希望从现有的`Message`复制标题，则可以使用其中一个“复制”方法，如下例所示：

```
Message<String> message3 = MessageBuilder.withPayload("test3")
        .copyHeaders(message1.getHeaders())
        .build();

Message<String> message4 = MessageBuilder.withPayload("test4")
        .setHeader("foo", 123)
        .copyHeadersIfAbsent(message1.getHeaders())
        .build();

assertEquals("bar", message3.getHeaders().get("foo"));
assertEquals(123, message4.getHeaders().get("foo"));
```

请注意，`copyHeadersIfAbsent`方法不会覆盖现有的值。此外，在前面的示例中，你可以看到如何使用`setHeader`设置任何用户定义的标头。最后，对于预定义的标题还有`set`方法可用，以及用于设置任何标题的非破坏性方法（`MessageHeaders`还为预定义的标题名称定义了常量）。

还可以使用`MessageBuilder`设置消息的优先级，如下例所示：

```
Message<Integer> importantMessage = MessageBuilder.withPayload(99)
        .setPriority(5)
        .build();

assertEquals(5, importantMessage.getHeaders().getPriority());

Message<Integer> lessImportantMessage = MessageBuilder.fromMessage(importantMessage)
        .setHeaderIfAbsent(IntegrationMessageHeaderAccessor.PRIORITY, 2)
        .build();

assertEquals(2, lessImportantMessage.getHeaders().getPriority());
```

只有在使用`PriorityChannel`时才考虑`priority`头（如下一章所述）。它被定义为`java.lang.Integer`。