Spring WebSocketStompClient: как десериализовать полезную нагрузку сообщения STOMP до универсального типа

#java #stomp #spring-messaging

#java #stomp #spring-обмен сообщениями

Вопрос:

Ищете лучший способ десериализовать полезную нагрузку STOMP-сообщений до универсальных типов.

У меня есть служба websocket, которая получает объект, сохраняет его и отправляет сохраненный объект с переносом сообщений всем подписанным клиентам.

В обычном JSON это должно выглядеть так:

Клиент отправляет объект:

 {
  "name": "New Facility Name"
}
  

Сообщение от службы, которое я пытаюсь обработать в клиенте:

 {
  "type": "ADD",
  "entity": {
    "id": 123,
    "name": "New Facility Name"
  }
}
  

Вот реализация Java, которая работает не так, как я ожидаю.

Сущность — JavaBean Facility с полями id и name .

Сообщение.

 public final class UpdateMessage<T> {

    private final Type type;
    private final T entity;

    @JsonCreator
    public UpdateMessage(@JsonProperty("type") Type type, @JsonProperty("entity") T entity) {
        this.type = type;
        this.entity = entity;
    }

    // getters

    public enum Type {
        ADD,
        DELETE,
        EDIT
    }
}
  

Тест.

 @RunWith(SpringRunner.class)
@SpringBootTest
public class WebsocketClientTest {

    private CompletableFuture<UpdateMessage<Facility>> messageCompletableFuture = new CompletableFuture<>();

    @Autowired
    private WebsocketClient client;

    @Before
    public void setUp() {
        client.connect();
    }

    @After
    public void setDown() {
        client.disconnect();
    }

    @Test
    public void trySpringResolvableType() throws InterruptedException, TimeoutException, ExecutionException {
        StompSession session = client.getSession();
        session.subscribe("/topic/facility/update", new StompFrameHandler() {
            @Override
            public Type getPayloadType(StompHeaders stompHeaders) {
                return ResolvableType.forClassWithGenerics(UpdateMessage.class, Facility.class).getType();
            }

            @Override
            public void handleFrame(StompHeaders stompHeaders, Object payload) {
                //noinspection unchecked
                messageCompletableFuture.complete((UpdateMessage<Facility>) payload);
            }
        });

        Facility facility = new Facility();
        facility.setName("New Facility Name");
        client.getSession().send("/app/facility/post", facility);

        UpdateMessage<Facility> message = messageCompletableFuture.get(9, SECONDS);

        /* Get 'java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to Facility' here: */
        assertEquals(facility.getName(), message.getEntity().getName());
    }
}
  

Выдает результат ‘java.lang.ClassCastException: java.util.LinkedHashMap не может быть приведен к объекту’.

Пробовал также Jackson’s TypeReference<T>

 @Override
public Type getPayloadType(StompHeaders stompHeaders) {
    return new TypeReference<UpdateMessage<Facility>>() {}.getType();
}
  

с тем же результатом и TypeFactory

 @Override
public Type getPayloadType(StompHeaders stompHeaders) {
    return TypeFactory.defaultInstance().constructParametricType(UpdateMessage.class, Facility.class);
}
  

который выдает

 org.springframework.messaging.converter.MessageConversionException: Unresolvable payload type [[simple type, class UpdateMessage<Facility>]] from handler type [class FacilityController$2]
  

От

 org.springframework.messaging.simp.stomp.DefaultStompSession.invokeHandler(StompFrameHandler, Message<byte[]>, StompHeaders)
  

Мне удалось заставить это работать, используя класс-оболочку

 public final class FacilityUpdateMessage {

    private final UpdateMessage<Facility> message;

    @JsonCreator
    public FacilityUpdateMessage(@JsonProperty("type") UpdateMessage.Type type,
                                 @JsonProperty("entity") Facility entity
    ) {
        message = new UpdateMessage<>(type, entity);
    }

    public UpdateMessage<Facility> getMessage() {
        return message;
    }
}
  
 @Override
public Type getPayloadType(StompHeaders stompHeaders) {
    return FacilityUpdateMessage.class;
}
  

но я не уверен, что это лучшее решение.