消息映射规则和约定

Spring 集成实现了一种灵活的机制,用于将消息映射到方法及其参数,无需提供额外的配置,而是依赖一些默认规则并定义某些约定。以下部分中的示例阐述了这些规则。

示例场景

以下示例显示了一个未注释的参数(对象或基元),该参数不是 MapProperties 对象,且具有非空返回类型

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(无论顺序如何)都映射到消息头,从而为您提供与 Message 结构交互的良好 POJO 方式。

以下示例未显示任何参数(无论返回什么)

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 方法,因此我们消除了歧义。