Add Jackson 2 support while retaining backward compatibility as much as possible. Introduce a new abstraction for JSON conversion. JIRA: https://jira.springsource.org/browse/INT-2831 INT-2831: Polishing * remove `JsonObjectMapperFactoryBean` * add @Deprecated constructors to `ObjectToJsonTransformer` and `JsonToObjectTransformer` Polishing Docs, compiler warnings.
376 lines
21 KiB
XML
376 lines
21 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="transformer"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>Transformer</title>
|
||
|
||
<section id="transformer-introduction">
|
||
<title>Introduction</title>
|
||
<para>
|
||
Message Transformers play a very important role in enabling the loose-coupling of Message Producers and Message
|
||
Consumers. Rather than requiring every Message-producing component to know what type is expected by the next
|
||
consumer, Transformers can be added between those components. Generic transformers, such as one that converts a
|
||
String to an XML Document, are also highly reusable.
|
||
</para>
|
||
<para>
|
||
For some systems, it may be best to provide a
|
||
<ulink url="http://www.eaipatterns.com/CanonicalDataModel.html">Canonical Data Model</ulink>, but Spring
|
||
Integration's general philosophy is not to require any particular format. Rather, for maximum flexibility, Spring
|
||
Integration aims to provide the simplest possible model for extension. As with the other endpoint types, the use
|
||
of declarative configuration in XML and/or Annotations enables simple POJOs to be adapted for the role of Message
|
||
Transformers. These configuration options will be described below.
|
||
<note>
|
||
For the same reason of maximizing flexibility, Spring does not require XML-based Message payloads.
|
||
Nevertheless, the framework does provide some convenient Transformers for dealing with XML-based payloads if
|
||
that is indeed the right choice for your application. For more information on those transformers, see
|
||
<xref linkend="xml"/>.
|
||
</note>
|
||
</para>
|
||
</section>
|
||
|
||
<section id="transformer-config">
|
||
<title>Configuring Transformer</title>
|
||
|
||
|
||
<section id="transformer-namespace">
|
||
<title>Configuring Transformer with XML</title>
|
||
<para>
|
||
The <transformer> element is used to create a Message-transforming endpoint. In addition to "input-channel"
|
||
and "output-channel" attributes, it requires a "ref". The "ref" may either point to an Object that contains the
|
||
@Transformer annotation on a single method (see below) or it may be combined with an explicit method name value
|
||
provided via the "method" attribute.
|
||
<programlisting language="xml"><![CDATA[<int:transformer id="testTransformer" ref="testTransformerBean" input-channel="inChannel"
|
||
method="transform" output-channel="outChannel"/>
|
||
<beans:bean id="testTransformerBean" class="org.foo.TestTransformer" />]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Using a "ref" attribute is generally recommended if the custom transformer handler implementation can be reused in
|
||
other <code><transformer></code> definitions. However if the custom transformer handler implementation should
|
||
be scoped to a single definition of the <code><transformer></code>, you can define an inner bean definition:
|
||
<programlisting language="xml"><![CDATA[<int:transformer id="testTransformer" input-channel="inChannel" method="transform"
|
||
output-channel="outChannel">
|
||
<beans:bean class="org.foo.TestTransformer"/>
|
||
</transformer>]]></programlisting>
|
||
</para>
|
||
<note>
|
||
<para>
|
||
Using both the "ref" attribute and an inner handler definition in the same <code><transformer></code>
|
||
configuration is not allowed, as it creates an ambiguous condition and will result in an Exception being thrown.
|
||
</para>
|
||
</note>
|
||
<para>
|
||
The method that is used for transformation may expect either the <interfacename>Message</interfacename> type or
|
||
the payload type of inbound Messages. It may also accept Message header values either individually or as a full
|
||
map by using the <code>@Header</code> and <code>@Headers</code> parameter annotations respectively. The return value of the method can be
|
||
any type. If the return value is itself a <interfacename>Message</interfacename>, that will be passed along to
|
||
the transformer's output channel.
|
||
</para>
|
||
<para>
|
||
As of Spring Integration 2.0, a Message Transformer's transformation method can no longer return <code>null</code>.
|
||
Returning <code>null</code> will result in an exception since a Message Transformer should always be expected to
|
||
transform each source Message into a valid target Message. In other words, a Message Transformer should not be used
|
||
as a Message Filter since there is a dedicated <filter> option for that. However, if you do need this type of
|
||
behavior (where a component might return NULL and that should not be considered an error), a
|
||
<emphasis>service-activator</emphasis> could be used. Its <code>requires-reply</code> value is FALSE by default,
|
||
but that can be set to TRUE in order to have Exceptions thrown for NULL return values as with the transformer.
|
||
</para>
|
||
|
||
<para>
|
||
<emphasis>Transformers and Spring Expression Language (SpEL)</emphasis>
|
||
</para>
|
||
|
||
<para>
|
||
Just like Routers, Aggregators and other components, as of Spring Integration 2.0 Transformers can also benefit from SpEL support
|
||
(http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html)
|
||
whenever transformation logic is relatively simple.
|
||
|
||
<programlisting language="xml"><![CDATA[<int:transformer input-channel="inChannel"
|
||
output-channel="outChannel"
|
||
expression="payload.toUpperCase() + '- [' + T(java.lang.System).currentTimeMillis() + ']'"/>]]></programlisting>
|
||
|
||
In the above configuration we are achieving a simple transformation of the <emphasis>payload</emphasis> with a simple SpEL
|
||
expression and without writing a custom transformer. Our <emphasis>payload</emphasis> (assuming String) will be
|
||
upper-cased and concatenated with the current timestamp with some simple formatting.
|
||
</para>
|
||
|
||
<para>
|
||
<emphasis>Common Transformers</emphasis>
|
||
</para>
|
||
<para>
|
||
There are also a few Transformer implementations available out of the box. Because, it is fairly common
|
||
to use the <methodname>toString()</methodname> representation of an Object, Spring Integration provides an
|
||
<classname>ObjectToStringTransformer</classname> whose output is a Message with a String payload. That String
|
||
is the result of invoking the toString() operation on the inbound Message's payload.
|
||
|
||
<programlisting language="xml"><![CDATA[<int:object-to-string-transformer input-channel="in" output-channel="out"/>]]></programlisting>
|
||
|
||
A potential example for this would be sending some arbitrary object to the 'outbound-channel-adapter' in the
|
||
<emphasis>file</emphasis> namespace. Whereas that Channel Adapter only supports String, byte-array, or
|
||
<classname>java.io.File</classname> payloads by default, adding this transformer immediately before the
|
||
adapter will handle the necessary conversion. Of course, that works fine as long as the result of the
|
||
<methodname>toString()</methodname> call is what you want to be written to the File. Otherwise, you can
|
||
just provide a custom POJO-based Transformer via the generic 'transformer' element shown previously.
|
||
<tip>
|
||
When debugging, this transformer is not typically necessary since the 'logging-channel-adapter' is capable
|
||
of logging the Message payload. Refer to <xref linkend="channel-wiretap"/> for more detail.
|
||
</tip>
|
||
<note>
|
||
<para>
|
||
The <emphasis>object-to-string-transformer</emphasis> is very simple; it invokes <code>toString()</code>
|
||
on the inbound payload. There are two exceptions to this (since 3.0): if the payload is a <code>char[]</code>,
|
||
it invokes <code>new String(payload)</code>; if the payload is a <code>byte[]</code>, it invokes
|
||
<code>new String(payload, charset)</code>, where <code>charset</code> is "UTF-8" by default. The
|
||
<code>charset</code> can be modified by supplying the <emphasis>charset</emphasis> attribute on
|
||
the transformer.
|
||
</para>
|
||
<para>
|
||
For more sophistication (such as selection of the charset dynamically, at runtime), you can use
|
||
a SpEL expression-based transformer instead; for example:
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[<int:transformer input-channel="in" output-channel="out"
|
||
expression="new java.lang.String(payload, headers['myCharset']" />]]></programlisting>
|
||
</note>
|
||
</para>
|
||
<para>
|
||
If you need to serialize an Object to a byte array or deserialize a byte array back into an Object,
|
||
Spring Integration provides symmetrical serialization transformers. These will use standard Java serialization
|
||
by default, but you can provide an implementation of Spring 3.0's Serializer or Deserializer strategies via the
|
||
'serializer' and 'deserializer' attributes, respectively.
|
||
|
||
<programlisting language="xml"><![CDATA[<int:payload-serializing-transformer input-channel="objectsIn" output-channel="bytesOut"/>
|
||
|
||
<int:payload-deserializing-transformer input-channel="bytesIn" output-channel="objectsOut"/>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
<emphasis>Object-to-Map Transformer</emphasis>
|
||
</para>
|
||
<para>
|
||
Spring Integration also provides <emphasis>Object-to-Map</emphasis> and <emphasis>Map-to-Object</emphasis> transformers which
|
||
utilize the Spring Expression Language (SpEL) to serialize and de-serialize the object graphs. The object hierarchy is introspected
|
||
to the most primitive types (String, int, etc.). The path to this type is described via SpEL, which becomes the <emphasis>key</emphasis> in the
|
||
transformed Map. The primitive type becomes the value.
|
||
</para>
|
||
<para>
|
||
For example:
|
||
<programlisting language="java"><![CDATA[public class Parent{
|
||
private Child child;
|
||
private String name;
|
||
// setters and getters are omitted
|
||
}
|
||
|
||
public class Child{
|
||
private String name;
|
||
private List<String> nickNames;
|
||
// setters and getters are omitted
|
||
}]]></programlisting>
|
||
... will be transformed to a Map which looks like this:
|
||
<code>{person.name=George, person.child.name=Jenna, person.child.nickNames[0]=Bimbo . . . etc}</code>
|
||
</para>
|
||
<para>
|
||
The SpEL-based Map allows you to describe the object structure without sharing the actual types allowing
|
||
you to restore/rebuild the object graph into a differently typed Object graph as long as you maintain the structure.
|
||
</para>
|
||
<para>
|
||
For example:
|
||
The above structure could be easily restored back to the following Object graph via the Map-to-Object transformer:
|
||
<programlisting language="java"><![CDATA[public class Father {
|
||
private Kid child;
|
||
private String name;
|
||
// setters and getters are omitted
|
||
}
|
||
|
||
public class Kid {
|
||
private String name;
|
||
private List<String> nickNames;
|
||
// setters and getters are omitted
|
||
}]]></programlisting>
|
||
</para>
|
||
<para>
|
||
To configure these transformers, Spring Integration provides namespace support
|
||
Object-to-Map:
|
||
<programlisting language="xml"><![CDATA[<int:object-to-map-transformer input-channel="directInput" output-channel="output"/>]]></programlisting>
|
||
Map-to-Object
|
||
<programlisting language="xml"><![CDATA[<int:map-to-object-transformer input-channel="input"
|
||
output-channel="output"
|
||
type="org.foo.Person"/>]]></programlisting>
|
||
or
|
||
<programlisting language="xml"><![CDATA[<int:map-to-object-transformer input-channel="inputA"
|
||
output-channel="outputA"
|
||
ref="person"/>
|
||
<bean id="person" class="org.foo.Person" scope="prototype"/>
|
||
]]></programlisting>
|
||
|
||
</para>
|
||
<note>
|
||
NOTE: 'ref' and 'type' attributes are mutually exclusive. You can only use one.
|
||
Also, if using the 'ref' attribute, you must point to a 'prototype' scoped bean, otherwise
|
||
a BeanCreationException will be thrown.
|
||
</note>
|
||
<para>
|
||
<emphasis>JSON Transformers</emphasis>
|
||
</para>
|
||
<para>
|
||
<emphasis>Object to JSON</emphasis> and <emphasis>JSON to Object</emphasis> transformers are provided.
|
||
</para>
|
||
<para>
|
||
<programlisting language="xml"><![CDATA[<int:object-to-json-transformer input-channel="objectMapperInput"/>]]></programlisting>
|
||
<programlisting language="xml"><![CDATA[<int:json-to-object-transformer input-channel="objectMapperInput"
|
||
type="foo.MyDomainObject"/>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
These use a vanilla Jackson ObjectMapper by default. If you wish to customize the ObjectMapper (for example,
|
||
to configure the 'ALLOW_COMMENTS' feature when parsing JSON), you can supply a reference to your custom ObjectMapper bean using
|
||
the object-mapper attribute.
|
||
</para>
|
||
<para>
|
||
<programlisting language="xml"><![CDATA[<int:json-to-object-transformer input-channel="objectMapperInput"
|
||
type="foo.MyDomainObject" object-mapper="customObjectMapper"/>]]></programlisting>
|
||
</para>
|
||
<note>
|
||
<para>
|
||
Beginning with version 3.0, the <code>object-mapper</code> attribute references an instance of a new
|
||
strategy interface <interfacename>JsonObjectMapper</interfacename>. This abstraction allows multiple
|
||
implementations of json mappers to be used. Implementations that wrap Jackson 1.x and Jackson 2 are
|
||
provided, with the version being detected on the classpath. These classes are
|
||
<classname>JacksonJsonObjectMapper</classname> and <classname>Jackson2JsonObjectMapper</classname>.
|
||
</para>
|
||
<para>
|
||
For backward compatibility, a simple Jackson 1.x <classname>ObjectMapper</classname> can be provided
|
||
instead of a <interfacename>JsonObjectMapper</interfacename>. This will be removed in a future release.
|
||
</para>
|
||
</note>
|
||
<para>
|
||
You may wish to consider using a <interfacename>FactoryBean</interfacename> or simple factory
|
||
method to create the <classname>JsonObjectMapper</classname> with
|
||
the required characteristics.
|
||
</para>
|
||
<para>
|
||
<programlisting language="java"><![CDATA[public class ObjectMapperFactory {
|
||
|
||
public static Jackson2JsonObjectMapper getMapper() {
|
||
ObjectMapper mapper = new ObjectMapper();
|
||
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
|
||
return new Jackson2JsonObjectMapper(mapper);
|
||
}
|
||
}]]></programlisting>
|
||
</para>
|
||
<para>
|
||
<programlisting language="xml"><![CDATA[<bean id="customObjectMapper" class="foo.ObjectMapperFactory"
|
||
factory-method="getMapper"/>]]></programlisting>
|
||
</para>
|
||
<important>
|
||
<para>
|
||
Beginning with version 2.2, the <code>object-to-json-transformer</code> sets the <emphasis>content-type</emphasis>
|
||
header to <code>application/json</code>, by default, if the input message does not already have that header
|
||
present.
|
||
</para>
|
||
<para>
|
||
It you wish to set the <emphasis>content type</emphasis> header to some other value, or explicitly overwrite any existing header
|
||
with some value (including <code>application/json</code>), use the <code>content-type</code>
|
||
attribute. If you wish to suppress the setting of the header, set the <code>content-type</code>
|
||
attribute to an empty string (<code>""</code>). This will result in a message with no <code>content-type</code>
|
||
header, unless such a header was present on the input message.
|
||
</para>
|
||
<para>
|
||
The behavior of adding the default header has a side affect - causing applications with the following
|
||
sequence to fail:
|
||
</para>
|
||
<para>
|
||
<code>->object-to-json-transformer->amqp-outbound-adapter----></code>
|
||
</para>
|
||
<para>
|
||
<code>---->amqp-inbound-adapter->json-to-object-transformer-></code>
|
||
</para>
|
||
<para>
|
||
This is because the default <classname>SimpleMessageConverter</classname> used by the inbound adapter doesn't
|
||
recognize this content type and the adapter emits a message with a <code>byte[]</code> payload instead of
|
||
<code>String</code>, which was the case with earlier versions.
|
||
</para>
|
||
<para>
|
||
If you are using this pattern, there are a number of ways to configure the environment so that JSON
|
||
conversion will be performed correctly.
|
||
</para>
|
||
<para>
|
||
One solution is to set the content type to a text type, so the inbound converter will convert the JSON to
|
||
String. This solution requires a change to just the outbound application.
|
||
</para>
|
||
<para>
|
||
<programlisting language="xml"><![CDATA[<object-to-json-transformer ... content-type="text/x-json"/>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
The second solution is to eliminate the json transformers altogether and use an
|
||
<classname>org.springframework.amqp.support.converter.JsonMessageConverter</classname> on both the
|
||
outbound and inbound adapters. This configures the adapters to perform the JSON conversion and
|
||
the transformers are not necessary. The converter on the outbound adapter adds
|
||
type information to the message properties; the inbound converter uses this type information for the conversion.
|
||
The converter is provided to the adapters using the <emphasis>message-converter</emphasis> attribute.
|
||
This solution requires a change to both the inbound and outbound applications.
|
||
</para>
|
||
<para>
|
||
The third solution is to eliminate the <emphasis>json-to-object-transformer</emphasis> in just
|
||
the inbound application and use an
|
||
<classname>org.springframework.amqp.support.converter.JsonMessageConverter</classname> on the
|
||
inbound adapter. The converter
|
||
is provided to the adapter using the <emphasis>message-converter</emphasis> attribute.
|
||
However, because there will be no type information in the message properties,
|
||
this also requires adding the <emphasis>defaultType</emphasis> to the converter, using the
|
||
same type as currently configured on the <emphasis>json-to-object-transformer</emphasis>.
|
||
This solution requires a change to just the inbound application. The configuration below shows
|
||
how to configure the message converter; it requires <code>spring-amqp</code> 1.1.3 or
|
||
above.
|
||
</para>
|
||
<programlisting language="xml"><![CDATA[<bean id="jsonConverterWithPOType"
|
||
class="org.springframework.amqp.support.converter.JsonMessageConverter">
|
||
<property name="classMapper">
|
||
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
|
||
<property name="defaultType"
|
||
value="foo.PurchaseOrder" />
|
||
</bean>
|
||
</property>
|
||
</bean>
|
||
|
||
<int-amqp:inbound-channel-adapter ... message-converter="jsonConverterWithPOType" ... />]]></programlisting>
|
||
</important>
|
||
</section>
|
||
|
||
<section id="transformer-annotation">
|
||
<title>Configuring a Transformer with Annotations</title>
|
||
<para>
|
||
The <interfacename>@Transformer</interfacename> annotation can also be added to methods that expect either the
|
||
<interfacename>Message</interfacename> type or the message payload type. The return value will be handled in the
|
||
exact same way as described above in the section describing the <transformer> element.
|
||
<programlisting language="java">@Transformer
|
||
Order generateOrder(String productId) {
|
||
return new Order(productId);
|
||
}</programlisting>
|
||
</para>
|
||
<para>
|
||
Transformer methods may also accept the @Header and @Headers annotations that is documented in <xref linkend="annotations"/>
|
||
<programlisting language="java">@Transformer
|
||
Order generateOrder(String productId, @Header("customerName") String customer) {
|
||
return new Order(productId, customer);
|
||
}</programlisting>
|
||
</para>
|
||
</section>
|
||
|
||
</section>
|
||
|
||
|
||
<section id="header-filter">
|
||
<title>Header Filter</title>
|
||
|
||
Some times your transformation use case might be as simple as removing a few headers.
|
||
For such a use case, Spring Integration provides a <emphasis>Header Filter</emphasis> which allows you to specify certain header names
|
||
that should be removed from the output Message (e.g. for security reasons or a value that was only needed temporarily).
|
||
Basically the <emphasis>Header Filter</emphasis> is the opposite of the <emphasis>Header Enricher</emphasis>.
|
||
The latter is discussed in <xref linkend="header-enricher"/>
|
||
|
||
<programlisting language="xml"><![CDATA[<int:header-filter input-channel="inputChannel"
|
||
output-channel="outputChannel" header-names="lastName, state"/>]]></programlisting>
|
||
|
||
As you can see, configuration of a <emphasis>Header Filter</emphasis> is quite simple. It is a typical endpoint with input/output channels
|
||
and a <code>header-names</code> attribute. That attribute accepts the names of the header(s) (delimited by commas if there are multiple)
|
||
that need to be removed. So, in the above example the headers named 'lastName' and 'state' will not be present on the outbound Message.
|
||
</section>
|
||
|
||
</section>
|