Files
spring-cloud-stream/multi/multi_content-type-management.html
2018-10-26 11:29:08 +02:00

78 lines
24 KiB
HTML

<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>9.&nbsp;Content Type Negotiation</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud-stream.html" title="Spring Cloud Stream Reference Guide"><link rel="up" href="multi__preface.html" title="Part&nbsp;I.&nbsp;Preface"><link rel="prev" href="multi__configuration_options.html" title="8.&nbsp;Configuration Options"><link rel="next" href="multi_schema-evolution.html" title="10.&nbsp;Schema Evolution Support"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">9.&nbsp;Content Type Negotiation</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__configuration_options.html">Prev</a>&nbsp;</td><th width="60%" align="center">Part&nbsp;I.&nbsp;Preface</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi_schema-evolution.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h2 class="title"><a name="content-type-management" href="#content-type-management"></a>9.&nbsp;Content Type Negotiation</h2></div></div></div><p>Data transformation is one of the core features of any message-driven microservice architecture. Given that, in Spring Cloud Stream, such data
is represented as a Spring <code class="literal">Message</code>, a message may have to be transformed to a desired shape or size before reaching its destination. This is required for two reasons:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">To convert the contents of the incoming message to match the signature of the application-provided handler.</li><li class="listitem">To convert the contents of the outgoing message to the wire format.</li></ol></div><p>The wire format is typically <code class="literal">byte[]</code> (that is true for the Kafka and Rabbit binders), but it is governed by the binder implementation.</p><p>In Spring Cloud Stream, message transformation is accomplished with an <code class="literal">org.springframework.messaging.converter.MessageConverter</code>.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>As a supplement to the details to follow, you may also want to read the following <a class="link" href="https://spring.io/blog/2018/02/26/spring-cloud-stream-2-0-content-type-negotiation-and-transformation" target="_top">blog post</a>.</p></td></tr></table></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_mechanics" href="#_mechanics"></a>9.1&nbsp;Mechanics</h2></div></div></div><p>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:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@StreamListener(Processor.INPUT)</span></em>
<em><span class="hl-annotation" style="color: gray">@SendTo(Processor.OUTPUT)</span></em>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> String handle(Person person) {..}</pre><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>For simplicity, we assume that this is the only handler in the application (we assume there is no internal pipeline).</p></td></tr></table></div><p>The handler shown in the preceding example expects a <code class="literal">Person</code> object as an argument and produces a <code class="literal">String</code> type as an output.
In order for the framework to succeed in passing the incoming <code class="literal">Message</code> as an argument to this handler, it has to somehow transform the payload of the <code class="literal">Message</code> type from the wire format to a <code class="literal">Person</code> type.
In other words, the framework must locate and apply the appropriate <code class="literal">MessageConverter</code>.
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 (<code class="literal">Person</code> 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 <code class="literal">MessageConverter</code>, the framework needs an additional piece of information.
That missing piece is <code class="literal">contentType</code>.</p><p>Spring Cloud Stream provides three mechanisms to define <code class="literal">contentType</code> (in order of precedence):</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><span class="strong"><strong>HEADER</strong></span>: The <code class="literal">contentType</code> can be communicated through the Message itself. By providing a <code class="literal">contentType</code> header, you declare the content type to use to locate and apply the appropriate <code class="literal">MessageConverter</code>.</li><li class="listitem"><p class="simpara"><span class="strong"><strong>BINDING</strong></span>: The <code class="literal">contentType</code> can be set per destination binding by setting the <code class="literal">spring.cloud.stream.bindings.input.content-type</code> property.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>The <code class="literal">input</code> segment in the property name corresponds to the actual name of the destination (which is &#8220;input&#8221; in our case). This approach lets you declare, on a per-binding basis, the content type to use to locate and apply the appropriate <code class="literal">MessageConverter</code>.</p></td></tr></table></div></li><li class="listitem"><span class="strong"><strong>DEFAULT</strong></span>: If <code class="literal">contentType</code> is not present in the <code class="literal">Message</code> header or the binding, the default <code class="literal">application/json</code> content type is used to
locate and apply the appropriate <code class="literal">MessageConverter</code>.</li></ol></div><p>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).</p><p>Another reason for making <code class="literal">application/json</code> 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.</p><p>When the non-void handler method returns, if the the return value is already a <code class="literal">Message</code>, that <code class="literal">Message</code> becomes the payload. However, when the return value is not a <code class="literal">Message</code>, the new <code class="literal">Message</code> is constructed with the return value as the payload while inheriting
headers from the input <code class="literal">Message</code> minus the headers defined or filtered by <code class="literal">SpringIntegrationProperties.messageHandlerNotPropagatedHeaders</code>.
By default, there is only one header set there: <code class="literal">contentType</code>. This means that the new <code class="literal">Message</code> does not have <code class="literal">contentType</code> header set, thus ensuring that the <code class="literal">contentType</code> can evolve.
You can always opt out of returning a <code class="literal">Message</code> from the handler method where you can inject any header you wish.</p><p>If there is an internal pipeline, the <code class="literal">Message</code> 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 <code class="literal">Message</code> is sent back to the output destination.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_content_type_versus_argument_type" href="#_content_type_versus_argument_type"></a>9.1.1&nbsp;Content Type versus Argument Type</h3></div></div></div><p>As mentioned earlier, for the framework to select the appropriate <code class="literal">MessageConverter</code>, it requires argument type and, optionally, content type information.
The logic for selecting the appropriate <code class="literal">MessageConverter</code> resides with the argument resolvers (<code class="literal">HandlerMethodArgumentResolvers</code>), 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 <code class="literal">MessageConverters</code> to see if any one of them can convert the payload.
As you can see, the <code class="literal">Object fromMessage(Message&lt;?&gt; message, Class&lt;?&gt; targetClass);</code>
operation of the MessageConverter takes <code class="literal">targetClass</code> as one of its arguments.
The framework also ensures that the provided <code class="literal">Message</code> always contains a <code class="literal">contentType</code> header.
When no contentType header was already present, it injects either the per-binding <code class="literal">contentType</code> header or the default <code class="literal">contentType</code> header.
The combination of <code class="literal">contentType</code> argument type is the mechanism by which framework determines if message can be converted to a target type.
If no appropriate <code class="literal">MessageConverter</code> is found, an exception is thrown, which you can handle by adding a custom <code class="literal">MessageConverter</code> (see <span class="quote">&#8220;<span class="quote"><a class="xref" href="multi_content-type-management.html#spring-cloud-stream-overview-user-defined-message-converters" title="9.3&nbsp;User-defined Message Converters">Section&nbsp;9.3, &#8220;User-defined Message Converters&#8221;</a></span>&#8221;</span>).</p><p>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 <code class="literal">Message&lt;?&gt;</code> or <code class="literal">Object</code> as an argument.
By declaring the target type to be <code class="literal">Object</code> (which is an <code class="literal">instanceof</code> everything in Java), you essentially forfeit the conversion process.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Do not expect <code class="literal">Message</code> to be converted into some other type based only on the <code class="literal">contentType</code>.
Remember that the <code class="literal">contentType</code> is complementary to the target type.
If you wish, you can provide a hint, which <code class="literal">MessageConverter</code> may or may not take into consideration.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_message_converters" href="#_message_converters"></a>9.1.2&nbsp;Message Converters</h3></div></div></div><p><code class="literal">MessageConverters</code> define two methods:</p><pre class="programlisting">Object fromMessage(Message&lt;?&gt; message, Class&lt;?&gt; targetClass);
Message&lt;?&gt; toMessage(Object payload, <em><span class="hl-annotation" style="color: gray">@Nullable</span></em> MessageHeaders headers);</pre><p>It is important to understand the contract of these methods and their usage, specifically in the context of Spring Cloud Stream.</p><p>The <code class="literal">fromMessage</code> method converts an incoming <code class="literal">Message</code> to an argument type.
The payload of the <code class="literal">Message</code> could be any type, and it is
up to the actual implementation of the <code class="literal">MessageConverter</code> to support multiple types.
For example, some JSON converter may support the payload type as <code class="literal">byte[]</code>, <code class="literal">String</code>, and others.
This is important when the application contains an internal pipeline (that is, input &#8594; handler1 &#8594; handler2 &#8594;. . . &#8594; output) and the output of the upstream handler results in a <code class="literal">Message</code> which may not be in the initial wire format.</p><p>However, the <code class="literal">toMessage</code> method has a more strict contract and must always convert <code class="literal">Message</code> to the wire format: <code class="literal">byte[]</code>.</p><p>So, for all intents and purposes (and especially when implementing your own converter) you regard the two methods as having the following signatures:</p><pre class="programlisting">Object fromMessage(Message&lt;?&gt; message, Class&lt;?&gt; targetClass);
Message&lt;<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">byte</span>[]&gt; toMessage(Object payload, <em><span class="hl-annotation" style="color: gray">@Nullable</span></em> MessageHeaders headers);</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_provided_messageconverters" href="#_provided_messageconverters"></a>9.2&nbsp;Provided MessageConverters</h2></div></div></div><p>As mentioned earlier, the framework already provides a stack of <code class="literal">MessageConverters</code> to handle most common use cases.
The following list describes the provided <code class="literal">MessageConverters</code>, in order of precedence (the first <code class="literal">MessageConverter</code> that works is used):</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><code class="literal">ApplicationJsonMessageMarshallingConverter</code>: Variation of the <code class="literal">org.springframework.messaging.converter.MappingJackson2MessageConverter</code>. Supports conversion of the payload of the <code class="literal">Message</code> to/from POJO for cases when <code class="literal">contentType</code> is <code class="literal">application/json</code> (DEFAULT).</li><li class="listitem"><code class="literal">TupleJsonMessageConverter</code>: <span class="strong"><strong>DEPRECATED</strong></span> Supports conversion of the payload of the <code class="literal">Message</code> to/from <code class="literal">org.springframework.tuple.Tuple</code>.</li><li class="listitem"><code class="literal">ByteArrayMessageConverter</code>: Supports conversion of the payload of the <code class="literal">Message</code> from <code class="literal">byte[]</code> to <code class="literal">byte[]</code> for cases when <code class="literal">contentType</code> is <code class="literal">application/octet-stream</code>. It is essentially a pass through and exists primarily for backward compatibility.</li><li class="listitem"><code class="literal">ObjectStringMessageConverter</code>: Supports conversion of any type to a <code class="literal">String</code> when <code class="literal">contentType</code> is <code class="literal">text/plain</code>.
It invokes Object&#8217;s <code class="literal">toString()</code> method or, if the payload is <code class="literal">byte[]</code>, a new <code class="literal">String(byte[])</code>.</li><li class="listitem"><code class="literal">JavaSerializationMessageConverter</code>: <span class="strong"><strong>DEPRECATED</strong></span> Supports conversion based on java serialization when <code class="literal">contentType</code> is <code class="literal">application/x-java-serialized-object</code>.</li><li class="listitem"><code class="literal">KryoMessageConverter</code>: <span class="strong"><strong>DEPRECATED</strong></span> Supports conversion based on Kryo serialization when <code class="literal">contentType</code> is <code class="literal">application/x-java-object</code>.</li><li class="listitem"><code class="literal">JsonUnmarshallingConverter</code>: Similar to the <code class="literal">ApplicationJsonMessageMarshallingConverter</code>. It supports conversion of any type when <code class="literal">contentType</code> is <code class="literal">application/x-java-object</code>.
It expects the actual type information to be embedded in the <code class="literal">contentType</code> as an attribute (for example, <code class="literal">application/x-java-object;type=foo.bar.Cat</code>).</li></ol></div><p>When no appropriate converter is found, the framework throws an exception. When that happens, you should check your code and configuration and ensure you did not miss anything (that is, ensure that you provided a <code class="literal">contentType</code> by using a binding or a header).
However, most likely, you found some uncommon case (such as a custom <code class="literal">contentType</code> perhaps) and the current stack of provided <code class="literal">MessageConverters</code>
does not know how to convert. If that is the case, you can add custom <code class="literal">MessageConverter</code>. See <a class="xref" href="multi_content-type-management.html#spring-cloud-stream-overview-user-defined-message-converters" title="9.3&nbsp;User-defined Message Converters">Section&nbsp;9.3, &#8220;User-defined Message Converters&#8221;</a>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="spring-cloud-stream-overview-user-defined-message-converters" href="#spring-cloud-stream-overview-user-defined-message-converters"></a>9.3&nbsp;User-defined Message Converters</h2></div></div></div><p>Spring Cloud Stream exposes a mechanism to define and register additional <code class="literal">MessageConverters</code>.
To use it, implement <code class="literal">org.springframework.messaging.converter.MessageConverter</code>, configure it as a <code class="literal">@Bean</code>, and annotate it with <code class="literal">@StreamMessageConverter</code>.
It is then apended to the existing stack of `MessageConverter`s.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>It is important to understand that custom <code class="literal">MessageConverter</code> implementations are added to the head of the existing stack.
Consequently, custom <code class="literal">MessageConverter</code> implementations take precedence over the existing ones, which lets you override as well as add to the existing converters.</p></td></tr></table></div><p>The following example shows how to create a message converter bean to support a new content type called <code class="literal">application/bar</code>:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@EnableBinding(Sink.class)</span></em>
<em><span class="hl-annotation" style="color: gray">@SpringBootApplication</span></em>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> SinkApplication {
...
<em><span class="hl-annotation" style="color: gray">@Bean</span></em>
<em><span class="hl-annotation" style="color: gray">@StreamMessageConverter</span></em>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> MessageConverter customMessageConverter() {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> MyCustomMessageConverter();
}
}
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> MyCustomMessageConverter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> AbstractMessageConverter {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> MyCustomMessageConverter() {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">super</span>(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> MimeType(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar"</span>));
}
<em><span class="hl-annotation" style="color: gray">@Override</span></em>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">boolean</span> supports(Class&lt;?&gt; clazz) {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> (Bar.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>.equals(clazz));
}
<em><span class="hl-annotation" style="color: gray">@Override</span></em>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> Object convertFromInternal(Message&lt;?&gt; message, Class&lt;?&gt; targetClass, Object conversionHint) {
Object payload = message.getPayload();
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> (payload <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">instanceof</span> Bar ? payload : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Bar((<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">byte</span>[]) payload));
}
}</pre><p>Spring Cloud Stream also provides support for Avro-based converters and schema evolution.
See <span class="quote">&#8220;<span class="quote"><a class="xref" href="multi_schema-evolution.html" title="10.&nbsp;Schema Evolution Support">Chapter&nbsp;10, <i>Schema Evolution Support</i></a></span>&#8221;</span> for details.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__configuration_options.html">Prev</a>&nbsp;</td><td width="20%" align="center"><a accesskey="u" href="multi__preface.html">Up</a></td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi_schema-evolution.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">8.&nbsp;Configuration Options&nbsp;</td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-stream.html">Home</a></td><td width="40%" align="right" valign="top">&nbsp;10.&nbsp;Schema Evolution Support</td></tr></table></div></body></html>