# 信息
# 消息
Spring IntegrationMessage
是一个通用的数据容器。任何对象都可以作为有效负载提供,并且每个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 ,但它实际上是一个只读实现。任何试图 put 映射中的值都会导致UnsupportedOperationException 。同样适用于 remove 和clear 。,因为消息可能传递给多个消费者, Map 的结构不能修改。同样,消息的有效负载 Object 在初始创建之后也不能set 。但是,标头值本身(或有效负载对象)的可变性是有意留给框架用户的决定。 |
---|
作为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/> MessageHeaders.<br/>TIMESTAMP<br/> | <br/> java.lang.Long<br/> | 消息创建的时间。 每次消息发生突变时都会更改。 |
<br/> MessageHeaders.<br/>REPLY_CHANNEL<br/> | <br/> java.lang.Object<br/>(String or<br/>MessageChannel)<br/> | 当没有配置显式输出通道且没有ROUTING_SLIP 或ROUTING_SLIP 已耗尽时,将向其发送回复(如果有的话)的通道。如果该值是 String ,则它必须表示 Bean 名称或已由ChannelRegistry. 生成 |
<br/> MessageHeaders.<br/>ERROR_CHANNEL<br/> | <br/> java.lang.Object<br/>(String or<br/>MessageChannel)<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/> | 指示消息何时过期。 框架不直接使用,但可以用一个表头 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。 参见幂等接收机 Enterprise 集成模式。 |
<br/> IntegrationMessageHeaderAccessor.<br/>CLOSEABLE_RESOURCE<br/> | <br/> java.io.Closeable<br/> | 如果消息与Closeable 相关联,则存在此消息头。一个示例是与使用 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/> | 如果入站端点支持它,则回调以接受、拒绝或请求消息。 参见延迟确认可收集消息源和MQTT 手册 ACKS 。 |
如下例所示,在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/> | 见布线滑移。 |
# 消息 ID 生成
当一条消息在应用程序中转换时,每当它发生突变(例如,通过转换器)时,都会分配一个新的消息 ID。消息 ID 是UUID
。从 Spring Integration3.0 开始,用于 IS Generation 的默认策略比以前的java.util.UUID.randomUUID()
实现更有效。它使用基于安全随机种子的简单随机数,而不是每次创建一个安全随机数。
可以通过声明在应用程序上下文中实现org.springframework.util.IdGenerator
的 Bean 来选择不同的 UUID 生成策略。
在类装入器中只能使用一个 UUID 生成策略, 这意味着,如果两个或更多的应用程序上下文在同一个类装入器中运行,那么它们共享相同的策略, 如果其中一个上下文更改了策略,它被所有上下文使用。 如果同一个类装入器中的两个或更多个上下文声明类型 org.springframework.util.IdGenerator 的 Bean,则它们必须都是同一个类的实例。否则,试图替换自定义策略的上下文将无法初始化。 如果策略相同,但是参数化后,在第一个要初始化的上下文中使用了策略。 |
---|
除了默认的策略之外,还提供了两个额外的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
属性(参见全局属性)来为框架组件定制DefaultMessageBuilderFactory
。当你不想填充一些开箱即用的标题时,这可能会很有用,例如contentType
由ObjectToJsonTransformer
(参见JSON 变形金刚)。
当尝试使用MessageBuilder
构建新消息时,将忽略这种头,并将特定的INFO
消息发送到日志中。
从版本 5.0 开始,消息传递网关,页眉 Enricher ,内容更丰富和头过滤器不允许你配置MessageHeaders.ID
和MessageHeaders.TIMESTAMP
使用DefaultMessageBuilderFactory
时的头名称,并抛出BeanInitializationException
。
# 报头传播
当消息产生端点(例如服务激活器)处理(和修改)消息时,通常将入站头传播到出站消息。一个例外是变压器,当完整的消息返回到框架时。在这种情况下,用户代码负责整个出站消息。当转换器只返回有效负载时,入站报头将被传播。此外,只有在出站消息中不存在头文件的情况下,才会对其进行传播,从而允许你根据需要更改头文件的值。
从版本 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和routers。 |
---|
# 消息实现
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
时,无需强制转换。
# theMessageBuilder
助手类
你可能注意到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
。