Spring in Action Covers Spring 5-1--11 — страница 39 из 63

...

@Override

public void sendOrder(Order order) {

    jms.send(

        orderQueue,

        session -> session.createObjectMessage(order));

}

Указание адресата с помощью объекта Destination, подобного этому, дает вам возможность настроить Destination с использованием не только имени пункта назначения. Но на практике вы почти никогда не будете указывать ничего, кроме имени пункта назначения. Часто проще просто отправить имя в качестве первого параметра send():

@Override

public void sendOrder(Order order) {

    jms.send(

        "tacocloud.order.queue",

session -> session.createObjectMessage(order));

}

Хотя метод send() не особенно сложен в использовании (особенно, когда MessageCreator задается как лямбда-выражение), добавляется небольшая сложность, требующая предоставления MessageCreator. Разве не проще, если вам нужно только указать объект, который должен быть отправлен (и, возможно, пункт назначения)? Это кратко описывает, как работает convertAndSend (). Давайте взглянем.

Конвертация сообщения перед отправкой

Метод convertAndSend() в JmsTemplates упрощает публикацию сообщений, устраняя необходимость предоставлять MessageCreator. Вместо этого вы передаете объект, который должен быть отправлен напрямую, в convertAndSend(), и перед отправкой объект будет преобразован в Message.

Например, следующая переопределение sendOrder() использует convertAndSend() для отправки Order в заданный пункт назначения:

@Override

public void sendOrder(Order order) {

    jms.convertAndSend("tacocloud.order.queue", order);

}

Как и метод send(), метод convertAndSend () примет значение Destination или String, чтобы указать адресата, или вы можете вообще не указывать адресата, чтобы отправить сообщение в пункт назначения по умолчанию.

Какую бы реализацию convertAndSend() вы ни выбрали, Order, переданный в convertAndSend(), перед отправкой преобразуется в Message. Под капотом это достигается с помощью реализации MessageConverter, которая выполняет грязную работу по преобразованию объектов в Message -ы.

НАСТРОЙКА КОНВЕРТЕРА СООБЩЕНИЙ

MessageConverter - это Spring-определнный интерфейс, который имеет только два метода для реализации:

public interface MessageConverter {

    Message toMessage(Object object, Session session)

        throws JMSException, MessageConversionException;

    Object fromMessage(Message message)

}

Хотя этот интерфейс достаточно прост для реализации, вам часто не нужно создавать собственную реализацию. Spring уже предлагает несколько реализаций, таких как описанные в таблице 8.3.

Таблица 8.3. Spring message конвертеры для общих задач преобразования (все в пакете org.springframework.jms.support.converter)

Message конвертер - Что он делает

MappingJackson2MessageConverter - Использует Jackson 2 JSON библиотеку конвертирования сообщений в и из JSON

MarshallingMessageConverter - Использует JAXB для конвертирования сообщений в и из XML

MessagingMessageConverter - Конвертирует Message из абстракции в и из Message используя базовый MessageConverter для полезной нагрузки и JmsHeaderMapper для сопоставления заголовков JMS в и из стандартных заголовков сообщений

SimpleMessageConverter - Преобразует String в и из TextMessage, байтовые массивы в и из BytesMessage, Maps в и из MapMessage, и Serializable объекты в и из ObjectMessage.

SimpleMessageConverter является значением по умолчанию, но требует, чтобы отправляемый объект реализовывал Serializable. Это может быть хорошей идеей, но вы можете предпочесть использовать один из других конвертеров сообщений, например MappingJackson2MessageConverter, чтобы избежать этого ограничения.

Чтобы применить другой конвертер сообщений, все, что вам нужно сделать, - это объявить экземпляр выбранного конвертера как bean-компонент. Например, следующее объявление компонента позволит использовать MappingJackson2MessageConverter вместо SimpleMessageConverter:

@Bean

public MappingJackson2MessageConverter messageConverter() {

MappingJackson2MessageConverter messageConverter =

        new MappingJackson2MessageConverter();

    messageConverter.setTypeIdPropertyName("_typeId");

    return messageConverter;

}

Обратите внимание, что вы вызвали setTypeIdPropertyName() для MappingJackson2MessageConverter, прежде чем возвращать его. Это очень важно, так как позволяет получателю знать, в какой тип конвертировать входящее сообщение. По умолчанию он будет содержать полное имя класса конвертируемого типа. Но это несколько негибко, требуя, чтобы получатель также имел тот же тип, с тем же полностью определенным именем класса.

Чтобы обеспечить большую гибкость, вы можете сопоставить синтетическое имя типа с реальным типом, вызвав setTypeIdMappings() в конвертере сообщений. Например, следующее изменение метода bean-объекта конвертера сообщений отображает синтетический идентификатор типа order в класс Order:

@Bean

public MappingJackson2MessageConverter messageConverter() {

    MappingJackson2MessageConverter messageConverter =

        new MappingJackson2MessageConverter();

    messageConverter.setTypeIdPropertyName("_typeId");

    Map> typeIdMappings = new HashMap>();

    typeIdMappings.put("order", Order.class);

    messageConverter.setTypeIdMappings(typeIdMappings);

    return messageConverter;

}

Вместо того, чтобы полное имя класса отправлялось в свойстве _typeId сообщения, будет отправлено значение order. В принимающем приложении будет сконфигурирован аналогичный конвертер сообщений, отображающий order в собственном понимании order-а. Эта реализация order может быть в другом пакете, иметь другое имя и даже иметь подмножество свойств Order отправителя.

POST-ОБРАБОТКА СООБЩЕНИЙ

Предположим, что в дополнение к своему прибыльному веб-бизнесу Taco Cloud решила открыть несколько магазинов в строительном секторе. Учитывая, что любой из их ресторанов также может быть центром для веб-бизнеса, им нужен способ сообщить источник заказа на кухню в ресторан. Это позволит кухонному персоналу использовать другой процесс для заказов в магазине, нежели для заказов через Интернет.

Было бы разумно добавить новое свойство источника в объект Order для переноса этой информации, установв его как WEB для заказов, размещенных в Интернете, и STORE для заказов, размещенных в магазинах. Но для этого потребуется изменить как класс Order на веб-сайте, так и класс Order для кухонного приложения, когда на самом деле это информация, которая требуется только для составителей тако.

Более простым решением было бы добавить пользовательский заголовок к сообщению для установки источника заказа. Если бы вы использовали метод send() для отправки тако-заказов, это можно легко сделать, вызвав setStringProperty() для объекта Message:

jms.send("tacocloud.order.queue",

    session -> {

        Message message = session.createObjectMessage(order);

        message.setStringProperty("X_ORDER_SOURCE", "WEB");

});

Проблема в том, что вы не используете send(). При выборе использования convertAndSend() объект Message создается в обертке, и у вас нет к нему доступа.

К счастью, есть способ настроить Message, созданное в обертке, до его отправки. Передав MessagePostProcessor в качестве последнего параметра для convertAndSend(), вы можете делать с сообщением все, что захотите, после его создания. Следующий код по-прежнему использует convertAndSend(), но использует MessagePostProcessor для добавления заголовка X_ORDER_SOURCE до отправки сообщения:

jms.convertAndSend("tacocloud.order.queue", order, new MessagePostProcessor() {

    @Override

    public Message postProcessMessage(Message message) throws JMSException {

        message.setStringProperty("X_ORDER_SOURCE", "WEB");

return message;

    }

});

Возможно, вы заметили, что MessagePostProcessor - это функциональный интерфейс. Это означает, что вы можете немного упростить его, заменив анонимный внутренний класс лямбда-выражением:

jms.convertAndSend("tacocloud.order.queue", order,

    message -> {

        message.setStringProperty("X_ORDER_SOURCE", "WEB");

    return message;

});

Хотя вам нужен только этот конкретный MessagePostProcessor для этого одного вызова метода convertAndSend(), вы можете использовать один и тот же MessagePostProcessor для нескольких различных вызовов convertAndSend(). В этих случаях, возможно, ссылка на метод является лучшим выбором, чем лямбда, во избежание ненужного дублирования кода:

@GetMapping("/convertAndSend/order")

public String convertAndSendOrder() {

    Order order = buildOrder();

    jms.convertAndSend("tacocloud.order.queue", order,

        this::addOrderSource);

    return "Convert and sent order";

}

private Message addOrderSource(Message message) throws JMSException {

    message.setStringProperty("X_ORDER_SOURCE", "WEB");

return message;

}

Теперь вы видели несколько способов отправки сообщений. Но не стоит отправлять сообщение, если никто его не получит. Давайте посмотрим, как вы можете получать сообщения с помощью Spring и JMS.

8.1.3 Получение JMS сообщений

Когда дело доходит до получения сообщений, у вас есть выбор pull (получения) модель, где ваш код запрашивает сообщение и ожидает его получение, или push (проталкивания) модель, в которой сообщения передаются в ваш код по мере их поступления.