Mechanics
为了更好地理解内容类型协商背后的机制和必要性,我们通过使用以下消息处理程序作为示例,来看一个非常简单的用例:
To better understand the mechanics and the necessity behind content-type negotiation, we take a look at a very simple use case by using the following message handler as an example:
public Function<Person, String> personFunction {..}
为了简单起见,我们假设这是应用程序中唯一的处理程序函数(我们假设没有内部管道)。 |
For simplicity, we assume that this is the only handler function in the application (we assume there is no internal pipeline). |
前面示例中显示的处理程序期望`Person`对象作为参数,并生成`String`类型作为输出。为了让框架成功地将传入的`Message`作为参数传递给此处理程序,它必须以某种方式将`Message`类型的有效负载从线格式转换为`Person`类型。换句话说,框架必须找到并应用适当的`MessageConverter`。为了实现此目的,框架需要用户的某些指示。其中一个指示已由处理程序方法本身的签名(Person`类型)提供。因此,理论上来说,这应该是(并且在某些情况下)足够的。但是,对于大多数用例,为了选择适当的`MessageConverter
,框架需要一些额外的信息。缺失的部分是`contentType`。
The handler shown in the preceding example expects a Person
object as an argument and produces a String
type as an output.
In order for the framework to succeed in passing the incoming Message
as an argument to this handler, it has to somehow transform the payload of the Message
type from the wire format to a Person
type.
In other words, the framework must locate and apply the appropriate MessageConverter
.
To accomplish that, the framework needs some instructions from the user.
One of these instructions is already provided by the signature of the handler method itself (Person
type).
Consequently, in theory, that should be (and, in some cases, is) enough.
However, for the majority of use cases, in order to select the appropriate MessageConverter
, the framework needs an additional piece of information.
That missing piece is contentType
.
Spring Cloud Stream 提供三种机制来定义`contentType`(按优先级顺序):
Spring Cloud Stream provides three mechanisms to define contentType
(in order of precedence):
-
HEADER: The
contentType
can be communicated through the Message itself. By providing acontentType
header, you declare the content type to use to locate and apply the appropriateMessageConverter
. -
BINDING: The
contentType
can be set per destination binding by setting thespring.cloud.stream.bindings.input.content-type
property.
属性名称中的 input
段对应于目标的实际名称(在本例中为“input”)。这种方法让您可以在每次绑定基础上声明用于定位和应用适当 MessageConverter
的内容类型。
The |
-
DEFAULT: If
contentType
is not present in theMessage
header or the binding, the defaultapplication/json
content type is used to locate and apply the appropriateMessageConverter
.
如前所述,前面的列表还演示了在发生平局时优先级顺序。例如,由标头提供的ContentType优先于任何其他ContentType。对于按绑定设置的ContentType也是如此,它本质上允许你覆盖默认ContentType。然而,它还提供了一个明智的默认值(这是从社区反馈中确定的)。
As mentioned earlier, the preceding list also demonstrates the order of precedence in case of a tie. For example, a header-provided content type takes precedence over any other content type. The same applies for a content type set on a per-binding basis, which essentially lets you override the default content type. However, it also provides a sensible default (which was determined from community feedback).
将`application/json`设为默认值的另一个原因源自分布式微服务架构驱动的互操作性要求,其中生产者和使用者不仅在不同的 JVM 中运行,而且还可以在不同的非 JVM 平台上运行。
Another reason for making application/json
the default stems from the interoperability requirements driven by distributed microservices architectures, where producer and consumer not only run in different JVMs but can also run on different non-JVM platforms.
当非空处理程序方法返回时,如果返回值已经是`Message`,则该`Message`将成为有效负载。但是,当返回值不是`Message`时,将使用返回值构建新`Message`作为有效负载,同时继承输入`Message`的标头,减去`SpringIntegrationProperties.messageHandlerNotPropagatedHeaders`定义或过滤的标头。默认情况下,那里只设置了一个标头:contentType
。这意味着新的`Message`没有设置`contentType`标头,从而确保`contentType`可以发展。你始终可以选择不从处理程序方法返回`Message`,在那里可以注入所需的任何标头。
When the non-void handler method returns, if the return value is already a Message
, that Message
becomes the payload. However, when the return value is not a Message
, the new Message
is constructed with the return value as the payload while inheriting
headers from the input Message
minus the headers defined or filtered by SpringIntegrationProperties.messageHandlerNotPropagatedHeaders
.
By default, there is only one header set there: contentType
. This means that the new Message
does not have contentType
header set, thus ensuring that the contentType
can evolve.
You can always opt out of returning a Message
from the handler method where you can inject any header you wish.
如果存在内部管道,则`Message`将通过相同的转换过程发送到下一个处理程序。但是,如果不存在内部管道或者你已经到达了其末尾,则`Message`会被发回输出目的地。
If there is an internal pipeline, the Message
is sent to the next handler by going through the same process of conversion. However, if there is no internal pipeline or you have reached the end of it, the Message
is sent back to the output destination.
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
)。
As mentioned earlier, for the framework to select the appropriate MessageConverter
, it requires argument type and, optionally, content type information.
The logic for selecting the appropriate MessageConverter
resides with the argument resolvers (HandlerMethodArgumentResolvers
), which trigger right before the invocation of the user-defined handler method (which is when the actual argument type is known to the framework).
If the argument type does not match the type of the current payload, the framework delegates to the stack of the
pre-configured MessageConverters
to see if any one of them can convert the payload.
As you can see, the Object fromMessage(Message<?> message, Class<?> targetClass);
operation of the MessageConverter takes targetClass
as one of its arguments.
The framework also ensures that the provided Message
always contains a contentType
header.
When no contentType header was already present, it injects either the per-binding contentType
header or the default contentType
header.
The combination of contentType
argument type is the mechanism by which framework determines if message can be converted to a target type.
If no appropriate MessageConverter
is found, an exception is thrown, which you can handle by adding a custom MessageConverter
(see User-defined Message Converters
).
但是,如果payload类型与处理程序方法声明的目标类型匹配,该怎么办?在这种情况下,不需要进行任何转换,并且有效负载会原样传递。虽然这听起来很简单,但请记住将`Message<?>`或`Object`作为参数的处理程序方法。通过将目标类型声明为`Object`(这是 Java 中所有东西的`instanceof`),你实际上放弃了转换过程。
But what if the payload type matches the target type declared by the handler method? In this case, there is nothing to convert, and the
payload is passed unmodified. While this sounds pretty straightforward and logical, keep in mind handler methods that take a Message<?>
or Object
as an argument.
By declaring the target type to be Object
(which is an instanceof
everything in Java), you essentially forfeit the conversion process.
不要期望 |
Do not expect |
Message Converters
MessageConverters 定义了两种方法:
MessageConverters
define two methods:
Object fromMessage(Message<?> message, Class<?> targetClass);
Message<?> toMessage(Object payload, @Nullable MessageHeaders headers);
了解这些方法及其用法(特别是 Spring Cloud Stream 的上下文中)的契约很重要。
It is important to understand the contract of these methods and their usage, specifically in the context of Spring Cloud Stream.
fromMessage
方法将传入的 Message
转换为参数类型。Message
的有效负载可以是任何类型,而且支持多种类型取决于 MessageConverter
的实际实现。例如,某些 JSON 转换器可能支持有效的负载类型,如 byte[]
、String
等。当应用程序包含内部管道(即 input → handler1 → handler2 →. . . → output)时,这非常重要,并且上游处理程序的输出导致 Message
,该 Message
可能不是初始线路格式。
The fromMessage
method converts an incoming Message
to an argument type.
The payload of the Message
could be any type, and it is
up to the actual implementation of the MessageConverter
to support multiple types.
For example, some JSON converter may support the payload type as byte[]
, String
, and others.
This is important when the application contains an internal pipeline (that is, input → handler1 → handler2 →. . . → output) and the output of the upstream handler results in a Message
which may not be in the initial wire format.
然而,toMessage
方法具有更严格的契约,并且必须始终将 Message
转换为线路格式:byte[]
。
However, the toMessage
method has a more strict contract and must always convert Message
to the wire format: byte[]
.
因此,出于所有意图和目的(尤其是在实现你自己的转换器时),你认为这两个方法具有以下签名:
So, for all intents and purposes (and especially when implementing your own converter) you regard the two methods as having the following signatures:
Object fromMessage(Message<?> message, Class<?> targetClass);
Message<byte[]> toMessage(Object payload, @Nullable MessageHeaders headers);