Невозможно установить канал ответа для заголовка сообщения в Spring интеграции

Я определенно должен чего-то упустить, поэтому ищу помощи здесь.

Я создаю простое приложение REST с использованием Spring Integration со следующим входящим шлюзом HTTP:

<!-- Gateway -->
    <int-http:inbound-gateway id="fruitQuotePOSTGateway"
                              request-channel="fruitQuotePOSTRequests"
                              supported-methods="POST"
                              path="/api/v1/fruit-quote"
                              request-payload-type="java.lang.String"
                              reply-timeout="10000"
                              reply-channel="fruitQuotePOSTResponses"
                              error-channel="applicationErrors">
        <int-http:request-mapping consumes="application/xml" produces="application/xml"/>
    </int-http:inbound-gateway>

Как только XML попадает в этот шлюз, он выполняет следующие простые шаги:

  • Преобразование для создания объекта JAXB, соответствующего входящему запросу
  • Расширение заголовка сообщения, которое считывает "uuid" из объекта JAXB и устанавливает его в заголовок сообщения SI (Spring Integration).
  • Преобразование для генерации XML-ответа вызывающему клиенту.

Для начала вот XML-конфигурация всего приложения (для краткости опущены пространства имен HTTP):

<!-- Gateway -->
    <int-http:inbound-gateway id="fruitQuotePOSTGateway"
                              request-channel="fruitQuotePOSTRequests"
                              supported-methods="POST"
                              path="/api/v1/fruit-quote"
                              request-payload-type="java.lang.String"
                              reply-timeout="10000"
                              reply-channel="fruitQuotePOSTResponses"
                              error-channel="applicationErrors">
        <int-http:request-mapping consumes="application/xml" produces="application/xml"/>
    </int-http:inbound-gateway>

    <!--
    - Generate fruit quote request JAXB from the incoming request
    - Create a header "requestUUID" by reading it from fruit quote request JAXB
    - Generate fruit quote acknowledgement response for the calling client
    -->

    <int:transformer input-channel="fruitQuotePOSTRequests"
               ref="fruitQuoteTransformation"
               method="generateFruitQuoteRequestJAXB"/>

    <int:header-enricher input-channel="requestUUIDEnrichment" output-channel="orderIDGeneration">
        <int:header name="requestUUID" expression="payload.getFruitQuoteRequestJAXB().getFRUITQUOTEREQUESTDATA().getUuid()"/>
    </int:header-enricher>

    <int:transformer input-channel="fruitQuoteAcknowledgementGeneration"
                     ref="fruitQuoteTransformation"
                     method="generateFruitQuoteAcknowledgement"
                     output-channel="fruitQuotePOSTResponses"/>

    <!-- Error handling -->
    <int:transformer input-channel="applicationErrors"
                     ref="fruitQuoteTransformation"
                     method="generateFruitQuoteAcknowledgementWithError"
                     output-channel="fruitQuotePOSTResponses"/>

    <!-- Channels -->
    <int:channel id="fruitQuotePOSTRequests"/>
    <int:channel id="requestUUIDEnrichment"/>
    <int:channel id="fruitQuotePOSTResponses"/>
    <int:channel id="fruitQuoteAcknowledgementGeneration"/>
    <int:channel id="applicationErrors"/>

Полезная нагрузка, передаваемая от одного шага к другому в приложении, представляет собой настраиваемый объект Builder, как показано ниже (без имени пакета):

import static java.util.Objects.nonNull;

public class FruiteQuoteComposite {
    private final FRUITQUOTEREQUEST fruitQuoteRequestJAXB;
    private final FruitQuoteApplicationException fruitQuoteApplicationException;
    private final Integer orderID;
    private final ErrorInformation errorInformation;

    private FruiteQuoteComposite(FruiteQuoteCompositeBuilder fruiteQuoteCompositeBuilder) {
        this.fruitQuoteRequestJAXB = fruiteQuoteCompositeBuilder.fruitQuoteRequestJAXB;
        this.fruitQuoteApplicationException = fruiteQuoteCompositeBuilder.fruitQuoteApplicationException;
        this.orderID = fruiteQuoteCompositeBuilder.orderID;
        this.errorInformation = fruiteQuoteCompositeBuilder.errorInformation;
    }

    public FruitQuoteApplicationException getFruitQuoteApplicationException() {
        return fruitQuoteApplicationException;
    }

    public FRUITQUOTEREQUEST getFruitQuoteRequestJAXB() {
        return fruitQuoteRequestJAXB;
    }

    public Integer getOrderID() {
        return orderID;
    }

    public ErrorInformation getErrorInformation() {
        return errorInformation;
    }

    public static class FruiteQuoteCompositeBuilder {
        private FRUITQUOTEREQUEST fruitQuoteRequestJAXB;
        private FruitQuoteApplicationException fruitQuoteApplicationException;
        private Integer orderID;
        private ErrorInformation errorInformation;

        public FruiteQuoteCompositeBuilder() {
        }

        public FruiteQuoteCompositeBuilder setFruitQuoteRequestJAXB(FRUITQUOTEREQUEST fruitQuoteRequestJAXB) {
            if (nonNull(fruitQuoteRequestJAXB)) {
                this.fruitQuoteRequestJAXB = fruitQuoteRequestJAXB;
            }

            return this;
        }

        public FruiteQuoteCompositeBuilder setFruitQuoteApplicationException(FruitQuoteApplicationException fruitQuoteApplicationException) {
            if (nonNull(fruitQuoteApplicationException)) {
                this.fruitQuoteApplicationException = fruitQuoteApplicationException;
            }

            return this;
        }

        public FruiteQuoteCompositeBuilder setOrderID(Integer orderID) {
            if(nonNull(orderID)) {
                this.orderID = orderID;
            }

            return this;
        }

        public FruiteQuoteCompositeBuilder setErrorInformation(ErrorInformation errorInformation) {
            if (nonNull( errorInformation )) {
                this.errorInformation = errorInformation;
            }
            return this;
        }

        public FruiteQuoteComposite build() {
            return new FruiteQuoteComposite(this);
        }
    }
}

Причина, по которой я не использовал «выходной канал» на трансформаторах, заключается в том, что я хотел явно выбрать маршрут replyChannel / outgoing внутри логики Java, выполняющей преобразование.

Например, внутри метода FruitQuoteTransformation.generateFruitQuoteRequestJAXB я установил один маршрут для успеха и другой маршрут для исключений / ошибок следующим образом:

public Message<FruiteQuoteComposite> generateFruitQuoteRequestJAXB(Message<String> fruitQuoteRequestMessage) {
        String fruitQuoteRequest = fruitQuoteRequestMessage.getPayload();
        Unmarshaller unmarshaller;
        FRUITQUOTEREQUEST fruitQuoteRequestJAXB;

        try {
            unmarshaller = requireNonNull(fruitQuoteRequestJaxbContext).createUnmarshaller();
            fruitQuoteRequestJAXB = (FRUITQUOTEREQUEST) requireNonNull(unmarshaller)
                    .unmarshal(new StringReader(fruitQuoteRequest));
        } catch (JAXBException jaxbException) {
            logger.error("JAXB Unmarshalling exception occurred with error code :: " + ERR_FRUIT_QUOTE_REQUEST_JAXB_TRANSFORMATION, jaxbException);
            FruitQuoteApplicationException fruitQuoteApplicationException = generateFruitQuoteApplicationException(ERR_FRUIT_QUOTE_REQUEST_JAXB_TRANSFORMATION, MESSAGE_FRUIT_QUOTE_INTERNAL_SYSTEM_ERROR);

            FruiteQuoteComposite outboundFruitQuoteComposite = new FruiteQuoteComposite.FruiteQuoteCompositeBuilder()
                    .setFruitQuoteApplicationException(fruitQuoteApplicationException)
                    .build();

            return withPayload(requireNonNull(outboundFruitQuoteComposite))
                    .setHeader(MessageHeaders.REPLY_CHANNEL, "applicationErrors")
                    .build();
        }

        FruiteQuoteComposite outboundFruitQuoteComposite = new FruiteQuoteComposite.FruiteQuoteCompositeBuilder()
                .setFruitQuoteRequestJAXB(fruitQuoteRequestJAXB)
                .build();

        return withPayload(requireNonNull(outboundFruitQuoteComposite))
                .setHeader(MessageHeaders.REPLY_CHANNEL, "requestUUIDEnrichment")
                .build();
    }
  • Мой первый вопрос. По какой-то причине вызов .setHeader работает не так, как ожидалось, и сообщение не переходит на следующий канал. Что-то мне не хватает? Результат тот же, даже когда я использую .setReplyChannelName.
  • Мой второй вопрос. Если есть решение вопроса 1), сохраняя общую конфигурацию SI на основе XML, есть ли альтернативный подход к настройке индивидуальных каналов ответа? Единственный вариант, который пришел мне в голову, - использовать маршрутизатор после каждого трансформатора, но это казалось слишком многословным.

Не могли бы вы помочь?


person bmylavar    schedule 10.09.2019    source источник


Ответы (1)


Вы никогда не должны связываться с replyChannel заголовком фреймворка; он не предназначен для целей маршрутизации.

replyChannel - это внутренний канал, уникальный для каждого сообщения, используемого для сопоставления ответа на запрос. Обычно вам не нужно явное reply-channel на входящем шлюзе; если вы это сделаете, он просто соединяется во время выполнения с заголовком replyChannel сообщения.

Состояния ошибки следует обрабатывать на error-channel шлюза, вместо этого генерируя исключение. Различные типы исключений могут сигнализировать о разных ошибках.

person Gary Russell    schedule 10.09.2019
comment
Ладно, это важный урок для меня! Используя вашу идею использования канала ошибок ближайшего шлюза в качестве подстраховки для всех исключений, для этого можно просто генерировать исключения на уровне приложения (из любого применимого метода преобразователя / активатора службы) и ожидать, что они будут попасть в канал ошибок шлюза как Message ‹MessagingException› с полезной нагрузкой MessagingException, сохраняющей базовое исключение, зависящее от приложения, в качестве причины? - person bmylavar; 10.09.2019
comment
Ваше понимание правильное. Действительно, ваше исключение будет причиной - person Artem Bilan; 10.09.2019
comment
Спасибо, Артем! Я выполню это и отправлю обратно. - person bmylavar; 10.09.2019
comment
@ArtemBilan Это работает! У вас есть еще один вопрос: когда в игру вступает канал ошибки шлюза, разве сообщение ‹MessagingException› не сохраняет заголовки из предыдущего сообщения? В частности, в потоке, подобном агрегатору splitter- ›gateway-› http-outbound-gateway- ›, я помещаю сгенерированный uuid в заголовки сообщений вывода разделителя, чтобы я мог повторно использовать их в качестве стратегии корреляции в агрегаторе. Моя идея состоит в том, чтобы объединить как сообщения об успехе, так и сообщения об ошибках (поступающие по каналу ошибок). В этом случае я также надеялся получить uuid из неудачных сообщений. - person bmylavar; 10.09.2019
comment
Поток в моем комментарии выше основан на строках ‹int: chain›, упомянутых @ArtemBilan в этом сообщении: stackoverflow.com/questions/41227506/ Я хотел бы агрегировать сообщения, поступающие как из пути processError, так и из пути успеха, надеюсь, с использованием настраиваемых UUID, которые установлены для всех сообщений, выходящих из разделителя. - person bmylavar; 10.09.2019
comment
Я получил здесь ответ: stackoverflow.com/questions/31778650/ Заданное сообщение ‹MessagingException› messagingExceptionMessage, messagingExceptionMessage.getPayload (). getFailedMessage (). getHeaders () произвел все заголовки неудавшегося сообщения! - person bmylavar; 10.09.2019
comment
Замечательно, что вы ищете и читаете (и отправляете сюда) быстрее, чтобы я подготовил для вас ответ ;-) - person Artem Bilan; 10.09.2019
comment
???? Конечно! Еще раз спасибо, Артем и Гэри. - person bmylavar; 10.09.2019