Mechanics

为了更好地理解内容类型协商背后的机制和必要性,我们通过使用以下消息处理程序作为示例,来看一个非常简单的用例:

public Function<Person, String> personFunction {..}

为了简单起见,我们假设这是应用程序中唯一的处理程序函数(我们假设没有内部管道)。

前面示例中显示的处理程序期望`Person`对象作为参数,并生成`String`类型作为输出。为了让框架成功地将传入的`Message`作为参数传递给此处理程序,它必须以某种方式将`Message`类型的有效负载从线格式转换为`Person`类型。换句话说,框架必须找到并应用适当的`MessageConverter`。为了实现此目的,框架需要用户的某些指示。其中一个指示已由处理程序方法本身的签名(Person`类型)提供。因此,理论上来说,这应该是(并且在某些情况下)足够的。但是,对于大多数用例,为了选择适当的`MessageConverter,框架需要一些额外的信息。缺失的部分是`contentType`。 Spring Cloud Stream 提供三种机制来定义`contentType`(按优先级顺序):

  1. HEADER:可以通过消息本身传递 contentType。通过提供 contentType 标头,便可声明要用于查找并应用合适 MessageConverter 的内容类型。

  2. BINDING: 可以通过设置 spring.cloud.stream.bindings.input.content-type 属性,根据每个目标绑定设置 contentType

属性名称中的 input 段对应于目标的实际名称(在本例中为“input”)。这种方法让您可以在每次绑定基础上声明用于定位和应用适当 MessageConverter 的内容类型。

  1. DEFAULT: 如果 contentType 不存在于 Message 头或绑定中,将使用默认 application/json 内容类型来查找和应用相应的 MessageConverter

如前所述,前面的列表还演示了在发生平局时优先级顺序。例如,由标头提供的ContentType优先于任何其他ContentType。对于按绑定设置的ContentType也是如此,它本质上允许你覆盖默认ContentType。然而,它还提供了一个明智的默认值(这是从社区反馈中确定的)。 将`application/json`设为默认值的另一个原因源自分布式微服务架构驱动的互操作性要求,其中生产者和使用者不仅在不同的 JVM 中运行,而且还可以在不同的非 JVM 平台上运行。 当非空处理程序方法返回时,如果返回值已经是`Message`,则该`Message`将成为有效负载。但是,当返回值不是`Message`时,将使用返回值构建新`Message`作为有效负载,同时继承输入`Message`的标头,减去`SpringIntegrationProperties.messageHandlerNotPropagatedHeaders`定义或过滤的标头。默认情况下,那里只设置了一个标头:contentType。这意味着新的`Message`没有设置`contentType`标头,从而确保`contentType`可以发展。你始终可以选择不从处理程序方法返回`Message`,在那里可以注入所需的任何标头。 如果存在内部管道,则`Message`将通过相同的转换过程发送到下一个处理程序。但是,如果不存在内部管道或者你已经到达了其末尾,则`Message`会被发回输出目的地。

Content Type versus Argument Type

如前所述,要让框架选择适当的 MessageConverter,它需要参数类型,并且可选地包含内容类型信息。选择适当 MessageConverter 的逻辑存在于参数解析器 (HandlerMethodArgumentResolvers) 中,该解析器在调用用户定义的处理程序方法之前立即触发(此时框架知道了实际参数类型)。如果参数类型与当前有效负载的类型不匹配,则该框架将委派给预先配置的 MessageConverters 堆栈,以查看它们中的任何一个是否可以转换有效负载。如您所见,MessageConverter 的 Object fromMessage(Message<?> message, Class<?> targetClass); 操作将其中的 targetClass 作为其参数之一。该框架还确保提供的 Message 始终包含一个 contentType 标头。当尚未存在 contentType 标头时,它将注入每个绑定 contentType 标头或默认 contentType 标头。contentType 参数类型的组合是框架确定消息是否可以转换为目标类型的机制。如果未找到适当的 MessageConverter,则会引发异常,您可以通过添加自定义 MessageConverter 来处理该异常(请参阅 User-defined Message Converters)。

但是,如果payload类型与处理程序方法声明的目标类型匹配,该怎么办?在这种情况下,不需要进行任何转换,并且有效负载会原样传递。虽然这听起来很简单,但请记住将`Message<?>`或`Object`作为参数的处理程序方法。通过将目标类型声明为`Object`(这是 Java 中所有东西的`instanceof`),你实际上放弃了转换过程。

不要期望 Message 仅基于 contentType 转换为其他类型。记住,contentType 与目标类型互补。如果您愿意,可以提供 MessageConverter 可能考虑或不考虑的提示。

Message Converters

MessageConverters 定义了两种方法:

Object fromMessage(Message<?> message, Class<?> targetClass);

Message<?> toMessage(Object payload, @Nullable MessageHeaders headers);

了解这些方法及其用法(特别是 Spring Cloud Stream 的上下文中)的契约很重要。

fromMessage 方法将传入的 Message 转换为参数类型。Message 的有效负载可以是任何类型,而且支持多种类型取决于 MessageConverter 的实际实现。例如,某些 JSON 转换器可能支持有效的负载类型,如 byte[]String 等。当应用程序包含内部管道(即 input → handler1 → handler2 →. . . → output)时,这非常重要,并且上游处理程序的输出导致 Message,该 Message 可能不是初始线路格式。

然而,toMessage 方法具有更严格的契约,并且必须始终将 Message 转换为线路格式:byte[]

因此,出于所有意图和目的(尤其是在实现你自己的转换器时),你认为这两个方法具有以下签名:

Object fromMessage(Message<?> message, Class<?> targetClass);

Message<byte[]> toMessage(Object payload, @Nullable MessageHeaders headers);