Spring 引导 JMS - 没有 _type 属性 的通用 JSON 消息

Spring Boot JMS - Generic JSON messages without _type property

我正在 Spring 引导应用程序中实施 JMS。一切进行得都很顺利。然而,我对 JSON 消息和 Java 对象之间的紧密耦合感到非常惊讶。我正在寻找更灵活的解决方案的方向。

通过示例并使用 MappingJackson2MessageConverter,只要您在同一个应用程序中发送和接收,一切都很好。在幕后,它与 java 对象紧密耦合。如果我有一个名为 person:

的简单 java 对象
package acme.receivingapp.dto;

public class Person {
    private String firstName;
    private String lastName;
...
}

当 JmsTemplate 将其转换为消息时,JSON 看起来足够通用:

{"firstName":"John", "lastName":"Doe"}

但是它包括这个 属性:

"_type" : "acme.superapp.dto.Person"

如果 JmsListener 没有使用 Java class,它会抛出异常。即使 class 在功能上与本示例中的相同,它实际上是相同的 class 但只是在不同的包中也是如此:

package wonderco.sendingapp.dto;

public class Person {
    private String firstName;
    private String lastName;
...
}

我们将从大型机、python 应用程序、.Net 等许多外部实体接收消息。我不能要求他们将我们的对象类型包含在 _type 属性.

我可以专门为 Person 对象创建自己的 MessageConverter,但是如果我们有数百条消息/java classes,那么拥有这么多消息转换器会很笨重。我需要设计一些更通用的东西,可以用于任何类型的 JSON 消息/java class.

在我走上设计自己的通用解决方案的道路之前,有没有更通用的东西像 Spring RestControllers 和 Spring RestTemplates 在 JSON 消息的意义上是否与非常具体的 Java class 密切相关?我觉得我不可能是第一个尝试解决这个问题的人。

我想我已经掌握了这件事。我会尝试解释它,希望能帮助下一个 Spring / JMS 的新手。

正如 M.Deinum 指出的那样,与 REST 端点不同,队列可能包含许多不同类型的消息。即使您的实现每个队列只有一种类型的消息。因为队列允许任意数量的不同消息,这是为所提供的 MappingJackson2MessageConverter 设计的。因为假设总会有多种类型的消息,所以必须有一种机制来确定如何将不同类型消息的 JSON 解组为正确类型的 Java 对象。

您将找到的所有使用 MappingJackson2MessageConverter 的示例都将包含此设置:

MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
 
converter.setTypeIdPropertyName("_type");

这告诉消息转换器在创建消息时在名为 _type 的 属性 中设置对象类型,或者在读取消息时从 属性 中读取对象类型。 _type 属性 没有任何魔力。这不是一个标准。这正是 Spring 人在他们的示例中使用的内容,然后无数人剪切并粘贴了它。因此,对于您自己的消息,您可以根据需要将其更改为更合适的 属性 名称。因此,在我的示例中,如果需要,我可能会调用 属性 acme_receivingapp_message_type。然后我会告诉向我发送消息的外部实体将 属性 包含在消息类型中。

默认情况下,MappingJackson2MessageConverter 会将对象类型写入您选择的任何 属性 名称(_type 或其他)作为完全限定的 class 名称。在我的示例中,它是 acme.receivingapp.dto.Person。收到消息时,它会查看类型 属性 以确定要从 JSON.

创建什么类型的 Java 对象

到目前为止非常简单,但如果向我发送消息的人不使用 Java,仍然不是很方便。即使我可以说服所有人将 acme.receivingapp.dto.Person 发送给我,如果我将 class 从 Person 重构为 Human 会怎样?或者甚至只是重组包?现在我必须回去告诉 1,000 个外部实体停止将 属性 作为 acme.receivingapp.dto.Person 发送,现在将其作为 acme.receivingapp.dto.Human?

发送

就像我在最初的问题中所说的那样,消息和 Java class 非常紧密地耦合在一起,这在处理外部 systems/entities 时不起作用。

我的问题的答案就在 **Mapping**Jackson2MessageConverter 消息转换器的名称中。那里的关键是“映射”。映射是指将消息类型映射到Javaclasses,这就是我们想要的。只是默认情况下,因为没有提供映射信息,所以 MappingJackson2MessageConverter 只是使用完全限定的 java class 名称来创建和接收消息。我们需要做的就是向消息转换器提供映射信息,以便它可以从友好的 message-types(例如“人”)映射到我们应用程序中的特定 classes(例如 acme.receivingapp.dto.Person ).

如果你想要你的外部 systems/entities 将向你发送消息以简单地包含 属性 acme_receivingapp_message_type : Person 并且你希望它解组为 acme.receivingapp.dto.Person 对象在你这边收到,你会像这样设置你的消息转换器:

@Bean
public MessageConverter jacksonJmsMessageConverter() {
    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    converter.setTargetType(MessageType.TEXT);
    converter.setTypeIdPropertyName("acme_receivingapp_message_type");

    // Set up a map to convert our friendly message types to Java classes.
    Map<String, Class<?>> typeIdMap = new HashMap<>();
    typeIdMap.put("Person", acme.receivingapp.dto.Person.class);
    converter.setTypeIdMappings(typeIdMap);

    return converter;
}

这解决了消息类型 属性 和 Java class 名称之间紧耦合的问题。但是,如果您只处理队列中的一种消息类型并且不希望发送消息的人必须包含任何 属性 来指示消息类型怎么办?嗯 MappingJackson2MessageConverter 根本不支持。我尝试在地图中使用“空”键,然后将 属性 保留在消息之外,不幸的是它不起作用。我希望它确实支持在 属性 不存在时使用的“空”映射。

如果您的队列只处理一种类型的消息,并且您不希望发件人必须包含特殊的 属性 来指示消息类型,您可能会想编写自己的消息转换器。该转换器将盲目地将 JSON 解组为 java class 您将始终要处理的那个。或者您可能选择仅将其作为 TextMessage 接收并在您的侦听器中解组。

希望这对某人有所帮助,因为我最初发现它很混乱。