跳到主要内容
版本:7.0.2

消息映射规则与约定

DeepSeek V3 中英对照 Message Mapping Rules and Conventions

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