Вышел Spring Integration Java DSL Milestone 2

Мы рады сообщить вам о выпуске второго промежуточного расширения Java DSL для Spring Integration!

Артефакт org.springframework.integration:spring-integration-java-dsl:1.0.0.M2 доступен в Spring IO Milestone Repository.

2 промежуточный релиз включает несколько исправлений ошибок, нововведений и улучшений.

Спасибо всем тем, кто участвовал в первом релизе, оставлял отзывы, находил проблемы и делился мыслями.

Вот краткое описание того, что изменилось по сравнению с первым промежуточным релизом:

Обработчик лямбда

Как вы могли заметить, использование лямбда в Java 8 — это мощный инструмент для создания удобного и понятного DSL. Одна из просьб сообщества, которую мы получили, была возможность описания лямбда-выражений через .handle() EIP-метод вместо того, чтобы описывать POJO и использовать их как метод объявления. Но было важно не потерять возможность «конвертации типов во время выполнения». Кроме того, вы не могли получать generic-тип для лямбд. После некоторого исследования, мы нашли решение с добавлением type аргумента. Следовательно, было добавлено несколько новых методов к IntegrationFlowBuilder:

<P> IntegrationFlowBuilder handle(GenericHandler<P> handler)

<P> IntegrationFlowBuilder handle(GenericHandler<P> handler,
        EndpointConfigurer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer) 


<P> IntegrationFlowBuilder handle(Class<P> payloadType, GenericHandler<P> handler)

<P> IntegrationFlowBuilder handle(Class<P> payloadType, GenericHandler<P> handler,
        EndpointConfigurer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer)

Если вы будете использовать метод с указанием payloadType аргумента и в handler лямбду, последний аргумент будет являться оболочкой LambdaMessageProcessor с ConversionService. А сообщение payload будет конвертировано в соответствующий type. Тем самым, мы получили более лучшую слабую связность. Ниже простой пример, демонстрирующий это:

@Bean
public IntegrationFlow integerFlow() {
    return IntegrationFlows.from("input")
            .<byte[], String>transform(p -> new String(p, "UTF-8"))
            .handle(Integer.class, (p, h) -> p * 2)
            .get();
}

ConversionService предотвращает появление ошибки ClassCastException: String cannot be cast to Integer.

Тот же дополнительный аргумент был добавлен и в другие EIP-методы с лямбдами: .transform(), .filter(), .split() и т.д.

Transformers Factory

Удобная, гибкая Transformers фабрика была добавлена, чтобы использовать как внутренний объект с .transform() EIP-методом:

@Bean
public IntegrationFlow transformFlow() {
    return IntegrationFlows.from("input")
            .transform(Transformers.xpath("/root/myJson", XPathEvaluationType.STRING_RESULT))
            .transform(Transformers.fromJson(MyPojo.class))
            .transform(Transformers.serializer())
            .get();
}

Это позволяет избежать неудобное написание кода с использованием сеттеров, а также сделать поток определения более простым. Отмечу, что Transformers может быть использован для определения целевых Transformer‘ов как @Bean‘ы и снова их использовать в определении IntegrationFlow. Тем не менее, DSL парсер управляет определением бинов для внутренних объектов, если они ещё не определены как бины.

.gateway() EIP-метод

Т.к. определение IntegrationFlow выглядит так же как и <chain> из Spring Integration XML, мы ввели .gateway() EIP-метод, который играет ту же роль, что и <gateway> внутри <chain> — отправляет сообщение requestChannel какому-нибудь другому потоку сообщений и ждет результата из replyChannel или из TemporaryReplyChannel по умолчанию:

@Bean
@DependsOn("gatewayRequestFlow")
public IntegrationFlow gatewayFlow() {
    return IntegrationFlows.from("gatewayInput")
            .gateway("gatewayRequest", g -> g.errorChannel("gatewayError").replyTimeout(10L))
            .get();
}

@Bean
public IntegrationFlow gatewayRequestFlow() {
    return IntegrationFlows.from("gatewayRequest")
            .filter("foo"::equals, f -> f.throwExceptionOnRejection(true))
            .<String, String>transform(String::toUpperCase)
            .get();
}

Адаптеры для протоколов

Конечно, основное назначение Spring Integration в обеспечении взаимодействия с другими внешними системами, где Protocol Adapters обеспечивают эту функциональность. С Spring Integration Java DSL вы можете использовать generic’и в определении бинов(@Bean) для любых адаптеров конечной системы(например, MarshallingWebServiceInboundGateway), но целью DSL является в обеспечении высокоуровнего API описания компонентов также, как это делают Spring Integration XML конфигурации.

Теперь, когда вы познакомились с возможностями наших Builder и Lambda, создадим что-нибудь на их основе. Классы были введены с набором статических методов делегировать IntegrationComponentSpec<S, P> реализацию. Классы могут быть рассмотрены как «Namespace Factories», потому что они играют ту же роль, что и XML namespace для компонентов из конкретного модуля Spring Integration для конкретного протокола. На текущий момент, Spring Integration Java DSL поддерживает только пространства имен для фабрик Amqp и Jms:

@Bean
public IntegrationFlow amqpFlow() {
    return IntegrationFlows.from(Amqp.inboundGateway(this.rabbitConnectionFactory, queue()))
            .transform("hello "::concat)
            .transform(String.class, String::toUpperCase)
            .get();
}

@Bean
public IntegrationFlow amqpOutboundFlow() {
    return IntegrationFlows.from("amqpOutboundInput")
            .handle(Amqp.outboundAdapter(this.amqpTemplate).routingKeyExpression("headers.routingKey"))
            .get();
}

@Bean
public IntegrationFlow jmsInboundFlow() {
    return IntegrationFlows
            .from(Jms.inboundAdapter(this.jmsConnectionFactory)
                    .configureJmsTemplate(t ->
                            t.deliveryPersistent(true)
                                    .jmsMessageConverter(myMessageConverter()))
                    .destination("jmsInbound"))
            .<String, String>transform(String::toUpperCase)
            .channel(this.jmsOutboundInboundReplyChannel())
            .get();
}

@Bean
public IntegrationFlow jmsOutboundGatewayFlow() {
    return IntegrationFlows.from("jmsOutboundGatewayChannel")
            .handle(Jms.outboundGateway(this.jmsConnectionFactory)
                        .replyContainer(c ->
                                    c.concurrentConsumers(3)
                                            .sessionTransacted(true))
                        .requestDestination("jmsPipelineTest"))
            .get();
}

Мы показали здесь использование пространства имен фабрик как объявления внутренних адаптеров, кроме того, они могут быть использованы из определений @Bean, делая цепочку IntegrationFlow методов более понятной.

Мы спросили мнение сообщества по поводу этих пространств имен фабрик прежде, чем потратить усилия на другое; мы также попытались оценить приоритеты для реализации поддержки адаптеров/шлюзов.

Убедитесь в наличии конкретной spring-integration-[PROTOCOL].jar зависимости в classpath, т.к. spring-integration-java-dsl объявляет их как optional, чтобы избежать накладных расходов на стороне конечного приложения.

Изменения DSL Parser

Тем не менее, основной целью этой версии М2 является критически важный вопрос о неправильном расположении DSL parser. Теперь он перемещен из IntegrationConfigurationBeanFactoryPostProcessor в IntegrationFlowBeanPostProcessor и Spring Integration Java DSL больше не влияет на контекст приложения — он просто следует определению жизненного цикла стандартного Spring бина. Возможно, вам придется внести некоторые изменения в существующие DSL приложения для этой версии.

В большинстве случаев ограничиваются channel auto-declaration, когда мы не определяем явно определение MessageChannel бина, но обращаемся к нему из компонентов интеграции. Если вы заметили, в .gateway() из примера выше мы использовали @DependsOn аннотацию. Это потому, что бины зарегистрированы и инициализированы по одному по порядку, как они определены в @Configuration классах. Поскольку мы не используем определения бинов для MessageChannel‘ов, контекст приложения не объявляет dependsOn для бина, который использует этот канал, а с другой стороны, мы не объявляем MessageChannel для всех, у нас есть только один выбор, который зависит от IntegrationFlow.

Таким образом, вы объявляете явно MessageChannel бины или используете @DependsOn с соответствующим объявлением IntegrationFlow бина для объявления последующих IntegrationFlow каналов.

Выводы

Для получения подробной информации обратитесь к справочному руководству. Также, посмотрите Запись вебинара: Spring Integration 4.0 — новая граница, где Spring Integration Java DSL представлена в режиме «live coding».

И как обычно: пожалуйста, делитесь своими мыслями и мнениями: StackOverflow(spring-integration), Spring JIRA.

comments powered by Disqus