消息映射规则和约定

Spring Integration 实现了一个灵活的机制,用于将消息映射到方法及其参数,而无需提供额外的配置,这依赖于一些默认规则和定义的约定。以下各节中的示例阐明了这些规则。

示例场景

以下示例显示了一个单个未注解的参数(对象或基本类型),它不是 MapProperties 对象,且具有非 void 返回类型

public String doSomething(Object o);

输入参数是消息的负载。如果参数类型与消息负载不兼容,则会尝试使用 Spring 3.0 提供的转换服务进行转换。返回值被合并为返回消息的负载。

以下示例显示了一个单个未注解的参数(对象或基本类型),它不是 MapProperties 对象,且具有 Message 返回类型

public Message doSomething(Object o);

输入参数是消息的负载。如果参数类型与消息负载不兼容,则会尝试使用 Spring 3.0 提供的转换服务进行转换。返回值是一个新构造的消息,该消息将发送到下一个目标。

以下示例显示了一个单个参数,该参数是消息(或其子类之一),且具有任意对象或基本类型的返回类型

public int doSomething(Message msg);

输入参数本身就是一个 Message。返回值成为发送到下一个目标的 Message 的负载。

以下示例显示了一个单个参数,该参数是 Message(或其子类之一),且具有 Message(或其子类之一)作为返回类型

public Message doSomething(Message msg);

输入参数本身就是一个 Message。返回值是一个新构造的 Message,该消息将发送到下一个目标。

以下示例显示了一个类型为 MapProperties 的单个参数,且具有 Message 作为返回类型

public Message doSomething(Map m);

这个示例有点意思。虽然起初看起来像是直接映射到消息头,但始终优先考虑 Message 负载。这意味着如果 Message 负载的类型是 Map,则此输入参数表示 Message 负载。但是,如果 Message 负载的类型不是 Map,则转换服务不会尝试转换负载,并且输入参数将映射到消息头。

以下示例显示了两个参数,其中一个参数是任意类型(对象或基本类型),它不是 MapProperties 对象,另一个参数的类型是 MapProperties 类型(无论返回类型如何)

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

此组合包含两个输入参数,其中一个参数的类型为 Map。非 Map 参数(无论顺序如何)都映射到 Message 负载,而 MapProperties(无论顺序如何)都映射到消息头,从而提供了一种使用 POJO 与 Message 结构交互的便捷方式。

以下示例显示没有参数(无论返回类型如何)

public String doSomething();

此消息处理程序方法基于发送到此处理程序连接到的输入通道的消息进行调用。但是,没有映射任何 Message 数据,因此 Message 充当调用处理程序的事件或触发器。输出根据前面描述的规则进行映射。

以下示例显示没有参数且返回类型为 void

public void soSomething();

此示例与上一个示例相同,但它不产生任何输出。

基于注解的映射

基于注解的映射是将消息映射到方法的最安全、最不容易产生歧义的方法。以下示例显示了如何将方法显式映射到头

public String doSomething(@Payload String s, @Header("someheader") String b)

正如您稍后将看到的,如果没有注解,此签名将导致歧义条件。但是,通过将第一个参数显式映射到 Message 负载,并将第二个参数映射到 someheader 消息头的值,我们避免了任何歧义。

以下示例与上一个示例几乎相同

public String doSomething(@Payload String s, @RequestParam("something") String b)

@RequestMapping 或任何其他非 Spring Integration 映射注解都无关紧要,因此会被忽略,从而导致第二个参数未映射。虽然第二个参数可以轻松地映射到负载,但只能有一个负载。因此,注解可以防止此方法产生歧义。

以下示例显示了另一个类似的方法,如果没有注解来澄清意图,它将是模棱两可的

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

唯一的区别是第一个参数隐式映射到消息负载。

以下示例显示了另一个签名,如果没有注解,它肯定会被视为模棱两可,因为它具有两个以上的参数

public String soSomething(@Headers Map m, @Header("something") Map f, @Header("someotherthing") String bar)

此示例尤其成问题,因为它的两个参数都是 Map 实例。但是,使用基于注解的映射,可以轻松避免歧义。在此示例中,第一个参数映射到所有消息头,而第二个和第三个参数映射到名为“something”和“someotherthing”的消息头的值。负载没有映射到任何参数。

复杂场景

以下示例使用多个参数

多个参数可能会在确定适当映射方面造成很多歧义。通常建议使用 @Payload@Header@Headers 注解方法参数。本节中的示例显示了导致引发异常的歧义条件。

public String doSomething(String s, int i)

这两个参数的权重相等。因此,无法确定哪个是负载。

以下示例显示了一个类似的问题,只不过有三个参数

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

虽然 Map 可以轻松地映射到消息头,但无法确定如何处理这两个 String 参数。

以下示例显示了另一个模棱两可的方法

public String foo(Map m, Map f)

虽然有人可能会争辩说一个 Map 可以映射到消息负载,另一个可以映射到消息头,但我们不能依赖顺序。

任何具有多个方法参数且这些参数不是 (Map, <T>) 并且参数未加注解的方法签名都会导致歧义条件并触发异常。

下一组示例分别显示了导致歧义的多个方法。

消息处理程序使用多个方法的映射基于前面(在示例中)描述的相同规则。但是,某些场景可能仍然看起来令人困惑。

以下示例显示了具有合法(可映射且无歧义)签名的多个方法

public class Something {
    public String doSomething(String str, Map m);

    public String doSomething(Map m);
}

(无论方法名称相同还是不同都没有关系)。Message 可以映射到任何一个方法。当消息负载可以映射到 str 并且消息头可以映射到 m 时,将调用第一个方法。第二个方法也可以作为一个候选者,只需将消息头映射到 m。更糟糕的是,这两个方法的名称相同。起初,这可能看起来模棱两可,因为以下配置

<int:service-activator input-channel="input" output-channel="output" method="doSomething">
    <bean class="org.things.Something"/>
</int:service-activator>

它之所以有效,是因为映射首先基于负载,然后是其他所有内容。换句话说,第一个参数可以映射到负载的方法优先于所有其他方法。

现在考虑一个备选示例,它会产生一个真正模棱两可的条件

public class Something {
    public String doSomething(String str, Map m);

    public String doSomething(String str);
}

这两个方法都具有可以映射到消息负载的签名。它们还具有相同的名称。此类处理程序方法将触发异常。但是,如果方法名称不同,则可以使用 method 属性影响映射(如下一个示例中所示)。以下示例显示了具有两个不同方法名称的相同示例

public class Something {
    public String doSomething(String str, Map m);

    public String doSomethingElse(String str);
}

以下示例显示了如何使用 method 属性来指示映射

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

因为配置显式映射了 doSomethingElse 方法,所以我们消除了歧义。