B.6 Message Mapping rules and conventions

Spring Integration implements a flexible facility to map Messages to Methods and their arguments without providing extra configuration by relying on some default rules as well as defining certain conventions.

Simple Scenarios

Single un-annotated parameter (object or primitive) which is not a Map/Properties with non-void return type;

public String foo(Object o);

Details:

Input parameter is Message Payload. If parameter type is not compatible with Message Payload an attempt will be made to convert it using Conversion Service provided by Spring 3.0. The return value will be incorporated as a Payload of the returned Message

Single un-annotated parameter (object or primitive) which is not a Map/Properties with Message return type;

public Message  foo(Object o);

Details:

Input parameter is Message Payload. If parameter type is not compatible with Message Payload an attempt will be made to convert it using Conversion Service provided by Spring 3.0. The return value is a newly constructed Message that will be sent to the next destination.

Single parameter which is a Message or its subclass with arbitrary object/primitive return type;

public int foo(Message  msg);

Details:

Input parameter is Message itself. The return value will become a payload of the Message that will be sent to the next destination.

Single parameter which is a Message or its subclass with Message or its subclass as a return type;

public Message foo(Message msg);

Details:

Input parameter is Message itself. The return value is a newly constructed Message that will be sent to the next destination.

Single parameter which is of type Map or Properties with Message as a return type;

public Message foo(Map m);

Details:

This one is a bit interesting. Although at first it might seem like an easy mapping straight to Message Headers, the preference is always given to a Message Payload. This means that if Message Payload is of type Map, this input argument will represent Message Payload. However if Message Payload is not of type Map, then no conversion via Conversion Service will be attempted and the input argument will be mapped to Message Headers.

Two parameters where one of them is arbitrary non-Map/Properties type object/primitive and another is Map/Properties type object (regardless of the return)

public Message foo(Map h, <T> t);

Details:

This combination contains two input parameters where one of them is of type Map. Naturally the non-Map parameters (regardless of the order) will be mapped to a Message Payload and the Map/Properties (regardless of the order) will be mapped to  Message Headers giving you a nice POJO way of interacting with Message structure.

No parameters (regardless of the return)

public String foo();

Details:

This Message Handler method will be invoked based on the Message sent to the input channel this handler is hooked up to, however no Message data will be mapped, thus making Message act as event/trigger to invoke such handlerThe output will be mapped according to the rules above

No parameters, void return

public void foo();

Details:

Same as above, but no output 

Annotation based mappings

Annotation based mapping is the safest and least ambiguous approach to map Messages to Methods. There wil be many pointers to annotation based mapping throughout this manual, however here are couple of examples:

public String foo(@Payload String s,  @Header("foo") String b) 

Very simple and explicite way of mapping Messages to method. As you'll see later on without annotation this signature would result in the ambiguous condition, however by explicitly mapping first argument to a Message Payload and second argument to a value of the 'foo' Message Header we have avoided ambiguity.

public String foo(@Payload String s,  @RequestParam("foo") String b) 

Looks almost identical to the previous example, however @RequestMapping or any other non-SI mapping annotation is irrelevant  and therefore will be ignored leaving the second parameter unmapped. And although the second parameters could easily be mapped to a Payload, there can only be one Payload, therefore this method becomes ambiguous. 

public String foo(String s,  @Header("foo") String b) 

The same as above. The only difference is that the first argument will be mapped to Message Payload implicitly.

public String foo(@Headers Map m,  @Header("foo")Map f, @Header("bar") String bar)

Yet another signature that would definitely be treated as ambiguous because it has more then 2 arguments, plus two of them are Maps, however with annotation-based mapping ambiguity is easily avoided. In this example the first argument is mapped to all the Message Headers, while second and third argument map to the values of Message Headers 'foo' and 'bar'.

Complex Scenarios

Multiple parameters:

Multiple parameters could create a lot of ambiguity with regards to determining the appropriate mappings. The general advice is to annotate your method parameters with @Payload and/or @Header/@Headers Below are some of the examples of ambiguous conditions which result in exception being raised.

public String foo(String s, int i)

- the two parameters are equal in weight, therefore no way to determine which one is a payload and what to do with another.

public String foo(String s, Map m, String b) 

- almost the same as above. Although Map could be easily mapped to Message Headers, there is no way to determine what to do with two Strings.

public String foo(Map m, Map f)

- although one might argue that one Map could be mapped to Message Payload and another one to Message Headers, it would be unreasonable to rely on the order (e.g., first is Payload, second Headers)

[Tip]Tip
Basically any method signature with more then one method argument which is not (Map, <T>) and those parameters are not annotated will result in the ambiguous condition thus triggering an exception.

Multiple methods:

Message Handlers with multiple methods are mapped based on the same rules that are described above, however some scenarios might still look confusing.

Multiple methods (same or different name) with legal (mappable) signatures:

public class Foo{
	public String foo(String str, Map m);

	public String foo(Map m)
}

As you can see, the Message could be mapped to either method. The first method would be invoked where Message Payload could be mapped to 'str'  and Message Headers could be mapped to 'm'. The second method could easily also be a candidate where only Message Headers are mapped to 'm'. To make meters worse both methods have the same name which at first might look very ambiguous considering the following configuration:

<si:service-activator input-channel="input" output-channel="output" method="foo">
	<bean class="org.bar.Foo"/>
</si:service-activator>

At this point it would be important to understand Spring Integration mapping Conventions where at the very core, mappings are based on Payload first and everything else next. In other words the method whose argument could be mapped to a Payload will take precedence over all other methods.

On the other hand let's look at slightly different example:

public class Foo{
	public String foo(String str, Map m);

	public String foo(String str)
}

If you look at it you can probably see a truly an ambiguous condition. In this example since both methods have signatures that could be mapped to a Message Payload. They also have the same name. Such handler will trigger an exception. However if method names were different you could influence the mapping with 'method' attribute (see below):

public class Foo{
	public String foo(String str, Map m);

	public String bar(String str)
}
<si:service-activator input-channel="input" output-channel="output" method="bar">
	<bean class="org.bar.Foo"/>
</si:service-activator>

Now there is no ambiguity since the configuration explicitly maps to 'bar' method which has no name conflicts.