Migrate Groovy DSL extension project (#3914)

* Migrate Groovy DSL extension project

* Upgrade to Groovy `4.0.5`
* Apply `groovy` plugin for Groovy module to be able to compile Groovy classes for DSL
* Document new feature

* * Restore `lambdaWrapper` for the `GroovyIntegrationFlowDefinition.handle()`
to be able to preserve a generic argument type

* * Migrate more Spock test methods to JUnit style

* * Add missed `assert` to Groovy tests

* Fix language in docs

Co-authored-by: Gary Russell <grussell@vmware.com>

* * Fix Copyright and `@since` in new classes
* Remove deprecated `IntegrationFlows` class mentions in the `groovy-dsl.adoc`

Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
Artem Bilan
2022-10-17 15:41:58 -04:00
committed by GitHub
parent fe06fbc241
commit 83f2a9c246
9 changed files with 2009 additions and 7 deletions

View File

@@ -67,7 +67,7 @@ ext {
ftpServerVersion = '1.2.0'
graalvmVersion = '22.2.0'
greenmailVersion = '2.0.0-alpha-2'
groovyVersion = '3.0.13'
groovyVersion = '4.0.5'
hamcrestVersion = '2.2'
hazelcastVersion = '5.1.3'
hibernateVersion = '6.1.3.Final'
@@ -167,6 +167,7 @@ allprojects {
mavenBom "io.micrometer:micrometer-tracing-bom:$micrometerTracingVersion"
mavenBom "org.apache.camel:camel-bom:$camelVersion"
mavenBom "org.testcontainers:testcontainers-bom:$testcontainersVersion"
mavenBom "org.apache.groovy:groovy-bom:$groovyVersion"
}
}
@@ -617,14 +618,17 @@ project('spring-integration-graphql') {
project('spring-integration-groovy') {
description = 'Spring Integration Groovy Support'
apply plugin: 'groovy'
dependencies {
api project(':spring-integration-scripting')
api "org.codehaus.groovy:groovy:$groovyVersion"
api 'org.apache.groovy:groovy'
api 'org.springframework:spring-context-support'
testImplementation 'org.springframework:spring-web'
testRuntimeOnly "org.codehaus.groovy:groovy-dateutil:$groovyVersion"
testRuntimeOnly 'org.apache.groovy:groovy-dateutil'
}
}
@@ -852,7 +856,7 @@ project('spring-integration-scripting') {
providedImplementation "org.graalvm.sdk:graal-sdk:$graalvmVersion"
testImplementation "org.jruby:jruby-complete:$jrubyVersion"
testImplementation "org.codehaus.groovy:groovy-jsr223:$groovyVersion"
testImplementation 'org.apache.groovy:groovy-jsr223'
testImplementation "org.python:jython-standalone:$jythonVersion"
testRuntimeOnly 'org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable'
@@ -1143,7 +1147,7 @@ task schemaZip(type: Zip) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
javaProjects.each { subproject ->
Properties schemas = new Properties();
Properties schemas = new Properties()
def shortName = subproject.name.replaceFirst("${rootProject.name}-", '')
if (subproject.name.endsWith('-core')) {
shortName = ''
@@ -1204,7 +1208,7 @@ task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) {
description = "Builds -${archiveClassifier} archive, containing all jars and docs, " +
"suitable for community download page."
ext.baseDir = "${project.name}-${project.version}";
ext.baseDir = "${project.name}-${project.version}"
from('src/dist') {
include 'readme.txt'

View File

@@ -0,0 +1,270 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.groovy.dsl
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import org.reactivestreams.Publisher
import org.springframework.integration.core.MessageSource
import org.springframework.integration.dsl.GatewayProxySpec
import org.springframework.integration.dsl.IntegrationFlow
import org.springframework.integration.dsl.IntegrationFlowBuilder
import org.springframework.integration.dsl.IntegrationFlowDefinition
import org.springframework.integration.dsl.MessageProducerSpec
import org.springframework.integration.dsl.MessageSourceSpec
import org.springframework.integration.dsl.MessagingGatewaySpec
import org.springframework.integration.dsl.SourcePollingChannelAdapterSpec
import org.springframework.integration.endpoint.MessageProducerSupport
import org.springframework.integration.gateway.MessagingGatewaySupport
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
import java.util.function.Consumer
import java.util.function.Supplier
/**
* The factory class for Spring Integration Groovy DSL closures.
*
* @author Artem Bilan
*
* @since 6.0
*/
@CompileStatic
class IntegrationGroovyDsl {
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL.
* @param flow the {@link Closure} for {@link IntegrationFlowDefinition}
*/
static IntegrationFlow integrationFlow(
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
{ IntegrationFlowDefinition flowDefinition ->
def delegate = new GroovyIntegrationFlowDefinition(flowDefinition)
flow.delegate = delegate
flow.resolveStrategy = Closure.DELEGATE_FIRST
flow(delegate)
} as IntegrationFlow
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(Class, Consumer)} factory method.
*/
static IntegrationFlow integrationFlow(
Class<?> serviceInterface,
@DelegatesTo(value = GatewayProxySpec, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.dsl.GatewayProxySpec')
Closure<?> gatewaySpec = null,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
Consumer<GatewayProxySpec> configurer = GroovyIntegrationFlowDefinition.createConfigurerIfAny(gatewaySpec)
buildIntegrationFlow(IntegrationFlow.from(serviceInterface, configurer), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(String, boolean)} factory method.
*/
static IntegrationFlow integrationFlow(
String channelName,
Boolean fixedSubscriber = false,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
buildIntegrationFlow(IntegrationFlow.from(channelName, fixedSubscriber), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(MessageChannel)} factory method.
*/
static IntegrationFlow integrationFlow(
MessageChannel channel,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
buildIntegrationFlow(IntegrationFlow.from(channel), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(MessageSource, Consumer)} factory method.
*/
static IntegrationFlow integrationFlow(
MessageSource<?> messageSource,
@DelegatesTo(value = SourcePollingChannelAdapterSpec, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = "org.springframework.integration.dsl.SourcePollingChannelAdapterSpec")
Closure<?> adapterSpec = null,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
Consumer<SourcePollingChannelAdapterSpec> configurer =
GroovyIntegrationFlowDefinition.createConfigurerIfAny(adapterSpec)
buildIntegrationFlow(IntegrationFlow.from(messageSource, configurer), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(org.springframework.integration.dsl.MessageSourceSpec, Consumer)} factory method.
*/
static IntegrationFlow integrationFlow(
MessageSourceSpec messageSourceSpec,
@DelegatesTo(value = SourcePollingChannelAdapterSpec, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = "org.springframework.integration.dsl.SourcePollingChannelAdapterSpec")
Closure<?> adapterSpec = null,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
Consumer<SourcePollingChannelAdapterSpec> configurer =
GroovyIntegrationFlowDefinition.createConfigurerIfAny(adapterSpec)
buildIntegrationFlow(IntegrationFlow.from(messageSourceSpec, configurer), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#fromSupplier(Supplier, Consumer)} factory method.
*/
static IntegrationFlow integrationFlow(
Closure<Object> source,
@DelegatesTo(value = SourcePollingChannelAdapterSpec, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.dsl.SourcePollingChannelAdapterSpec')
Closure<?> adapterSpec = null,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
Consumer<SourcePollingChannelAdapterSpec> configurer =
GroovyIntegrationFlowDefinition.createConfigurerIfAny(adapterSpec)
buildIntegrationFlow(IntegrationFlow.fromSupplier(source, configurer), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(Publisher)} factory method.
*/
static IntegrationFlow integrationFlow(
Publisher<? extends Message<?>> publisher,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
buildIntegrationFlow(IntegrationFlow.from(publisher), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(MessagingGatewaySupport)} factory method.
*/
static IntegrationFlow integrationFlow(
MessagingGatewaySupport gateway,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
buildIntegrationFlow(IntegrationFlow.from(gateway), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(org.springframework.integration.dsl.MessagingGatewaySpec)} factory method.
*/
static IntegrationFlow integrationFlow(
MessagingGatewaySpec gatewaySpec,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
buildIntegrationFlow(IntegrationFlow.from(gatewaySpec), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(MessageProducerSupport)} factory method.
*/
static IntegrationFlow integrationFlow(
MessageProducerSupport producer,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
buildIntegrationFlow(IntegrationFlow.from(producer), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(org.springframework.integration.dsl.MessageProducerSpec)} factory method.
*/
static IntegrationFlow integrationFlow(
MessageProducerSpec producerSpec,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
buildIntegrationFlow(IntegrationFlow.from(producerSpec), flow)
}
/**
* Functional {@link IntegrationFlow} definition in Groovy DSL for
* {@link IntegrationFlow#from(IntegrationFlow)} factory method.
*/
static IntegrationFlow integrationFlow(
IntegrationFlow sourceFlow,
@DelegatesTo(value = GroovyIntegrationFlowDefinition, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType,
options = 'org.springframework.integration.groovy.dsl.GroovyIntegrationFlowDefinition')
Closure<?> flow) {
buildIntegrationFlow(IntegrationFlow.from(sourceFlow), flow)
}
private static IntegrationFlow buildIntegrationFlow(IntegrationFlowBuilder flowBuilder, Closure<?> flow) {
flow.delegate = new GroovyIntegrationFlowDefinition(flowBuilder)
flow.resolveStrategy = Closure.DELEGATE_FIRST
flow()
flowBuilder.get()
}
private IntegrationGroovyDsl() {
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides Spring Integration Groovy DSL.
*/
package org.springframework.integration.groovy.dsl

View File

@@ -0,0 +1,306 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.groovy.dsl.test
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.BeanFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.integration.channel.FluxMessageChannel
import org.springframework.integration.channel.QueueChannel
import org.springframework.integration.config.EnableIntegration
import org.springframework.integration.dsl.IntegrationFlow
import org.springframework.integration.dsl.IntegrationFlowDefinition
import org.springframework.integration.dsl.Pollers
import org.springframework.integration.dsl.Transformers
import org.springframework.integration.dsl.context.IntegrationFlowContext
import org.springframework.integration.handler.LoggingHandler
import org.springframework.integration.scheduling.PollerMetadata
import org.springframework.integration.support.MessageBuilder
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
import org.springframework.messaging.MessageHeaders
import org.springframework.messaging.PollableChannel
import org.springframework.messaging.support.GenericMessage
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
import reactor.core.publisher.Flux
import reactor.test.StepVerifier
import java.time.Duration
import java.util.function.Function
import static org.springframework.integration.groovy.dsl.IntegrationGroovyDsl.integrationFlow
/**
* @author Artem Bilan
*
* @since 6.0
*/
@SpringJUnitConfig
@DirtiesContext
class GroovyDslTests {
@Autowired
private BeanFactory beanFactory
@Autowired
private IntegrationFlowContext integrationFlowContext
@Autowired
private PollableChannel pollerResultChannel
@Autowired
@Qualifier('requestReplyFlow.input')
private MessageChannel requestReplyFlowInput
@Autowired
private MessageChannel requestReplyFixedFlowInput
@Autowired
@Qualifier('functionGateway')
private Function<byte[], String> upperCaseFunction
@Test
void 'when application starts, it emits message to pollerResultChannel'() {
assert this.pollerResultChannel.receive(10000) != null
assert this.pollerResultChannel.receive(10000) != null
}
@Test
void 'requestReplyFlow has to reply'() {
def replyChannel = new QueueChannel()
def testMessage =
MessageBuilder.withPayload('hello')
.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel)
.build()
this.requestReplyFlowInput.send(testMessage)
assert replyChannel.receive(1000).payload == 'HELLO'
}
@Test
void 'requestReplyFixedFlow has to reply'() {
def replyChannel = new QueueChannel()
def testMessage =
MessageBuilder.withPayload(4)
.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel)
.build()
this.requestReplyFixedFlowInput.send(testMessage)
assert replyChannel.receive(1000).payload == 16
}
@Test
void 'uppercase function'() {
assert this.upperCaseFunction.apply('test'.bytes) == 'TEST'
}
@Test
void 'reactive publisher flow'() {
def fluxChannel = new FluxMessageChannel()
def verifyLater =
StepVerifier
.create(Flux.from(fluxChannel).map { it.payload })
.expectNext(4, 6)
.thenCancel()
.verifyLater()
def publisher = Flux.just(2, 3).map { new GenericMessage<>(it) }
def integrationFlow =
integrationFlow(publisher)
{
transform Message<Integer>, { it.payload * 2 }, { id 'foo' }
channel fluxChannel
}
def registration = this.integrationFlowContext.registration(integrationFlow).register()
verifyLater.verify(Duration.ofSeconds(10))
registration.destroy()
}
@Autowired
@Qualifier('scatterGatherFlow.input')
private MessageChannel scatterGatherFlowInput
@Test
void 'Scatter-Gather'() {
def replyChannel = new QueueChannel()
def request =
MessageBuilder.withPayload("foo")
.setReplyChannel(replyChannel)
.build()
this.scatterGatherFlowInput.send(request)
def bestQuoteMessage = replyChannel.receive(10000)
assert (bestQuoteMessage?.payload as List).size() >= 1
}
@Autowired
@Qualifier('oddFlow.input')
private MessageChannel oddFlowInput
@Test
void 'oddFlow must reply'() {
def replyChannel = new QueueChannel()
def testMessage =
MessageBuilder.withPayload('test')
.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel)
.build()
this.oddFlowInput.send(testMessage)
assert replyChannel.receive(1000).payload == 'odd'
}
@Autowired
@Qualifier('flowLambda.input')
private MessageChannel flowLambdaInput
@Autowired
private PollableChannel wireTapChannel
@Test
void 'flow from lambda'() {
def replyChannel = new QueueChannel()
def message = MessageBuilder.withPayload('test').setReplyChannel(replyChannel).build()
this.flowLambdaInput.send message
assert replyChannel.receive(10_000)?.payload == 'TEST'
assert this.wireTapChannel.receive(10_000)?.payload == 'test'
}
@Configuration
@EnableIntegration
static class Config {
@Bean(PollerMetadata.DEFAULT_POLLER)
poller() {
Pollers.fixedDelay(1000).get()
}
@Bean
someFlow() {
integrationFlow { 'test' }
{
log LoggingHandler.Level.WARN, 'test.category'
channel { queue 'pollerResultChannel' }
}
}
@Bean
requestReplyFlow() {
integrationFlow {
fluxTransform { it.map { it } }
transform String, { it.toUpperCase() }
}
}
@Bean
requestReplyFixedFlow() {
integrationFlow 'requestReplyFixedFlowInput', true,
{
handle Integer, { p, h -> p**2 }
}
}
@Bean
functionFlow() {
integrationFlow Function<byte[], String>,
{ beanName 'functionGateway' },
{
transform Transformers.objectToString(), { id 'objectToStringTransformer' }
transform String, { it.toUpperCase() }
split Message<?>, { it.payload }
split Object, { it }, { id 'splitterEndpoint' }
resequence()
aggregate {
id 'aggregator'
outputProcessor { it.one }
}
}
}
@Bean
scatterGatherFlow() {
integrationFlow {
scatterGather(
{
applySequence true
recipientFlow({ true }, recipientSubFlow())
recipientFlow({ true },
integrationFlow { handle Void, { p, h -> Math.random() * 10 } })
recipientFlow({ true },
integrationFlow { handle Void, { p, h -> Math.random() * 10 } })
},
{
releaseStrategy {
it.size() == 3 || it.messages.any { it.payload as Double > 5 }
}
})
{
gatherTimeout 10_000
}
}
}
static recipientSubFlow() {
integrationFlow { handle Void, { p, h -> Math.random() * 10 } }
}
@Bean
IntegrationFlow oddFlow() {
{ IntegrationFlowDefinition flow ->
flow.handle(Object, { p, h -> 'odd' })
}
}
@Bean
flowLambda() {
integrationFlow {
filter String, { it == 'test' }, { id 'filterEndpoint' }
wireTap integrationFlow {
channel { queue 'wireTapChannel' }
}
delay 'delayGroup', { defaultDelay 100 }
transform String, { it.toUpperCase() }
}
}
@Bean
flowFromSupplier() {
integrationFlow({ 'bar' }, { poller { it.fixedDelay(10).maxMessagesPerPoll(1) } }) {
channel { queue 'fromSupplierQueue' }
}
}
}
}

View File

@@ -4,6 +4,7 @@
The Spring Integration Java configuration and DSL provides a set of convenient builders and a fluent API that lets you configure Spring Integration message flows from Spring `@Configuration` classes.
(See also <<./kotlin-dsl.adoc#kotlin-dsl,Kotlin DSL>>.)
(See also <<./groovy-dsl.adoc#groovy-dsl,Groovy DSL>>.)
The Java DSL for Spring Integration is essentially a facade for Spring Integration.
The DSL provides a simple way to embed Spring Integration Message Flows into your application by using the fluent `Builder` pattern together with existing Java configuration from Spring Framework and Spring Integration.

View File

@@ -0,0 +1,103 @@
[[groovy-dsl]]
== Groovy DSL
The Groovy DSL is a wrapper and extension to <<./dsl.adoc#java-dsl,Java DSL>>.
The main goal we pursue here is to make Spring Integration development on Groovy as smooth and straightforward as is it possible with interoperability with existing Java DSL and some Groovy extensions or language-specific structures.
The implementation is a part of <<./groovy.adoc#groovy,Groovy Support>> module.
All you need to get started is just an import for `import static org.springframework.integration.groovy.dsl.IntegrationGroovyDsl.integrationFlow` - a class containing overloaded factory methods for the Groovy DSL.
For `IntegrationFlow` definitions as lambdas we typically don't need anything else from Groovy and just declare a bean like this:
====
[source, groovy]
----
@Bean
IntegrationFlow oddFlow() {
{ IntegrationFlowDefinition flow ->
flow.handle(Object, { p, h -> 'odd' })
}
}
----
====
In this case Groovy understands that the closure should be translated into an `IntegrationFlow` anonymous instance and the target Java DSL processor parses this construction properly into Java objects.
As an alternative to the construction above and for consistency with use-cases explained below, the `spring-integration-groovy` module provides a Groovy-specific DSL for declaring integration flows in a *builder* pattern style:
====
[source, groovy]
----
@Bean
flowLambda() {
integrationFlow {
filter String, { it == 'test' }, { id 'filterEndpoint' }
wireTap integrationFlow {
channel { queue 'wireTapChannel' }
}
delay 'delayGroup', { defaultDelay 100 }
transform String, { it.toUpperCase() }
}
}
----
====
Such a global `integrationFlow()` function expects a closure in the builder style for a `GroovyIntegrationFlowDefinition` (a Groovy wrapper for the `IntegrationFlowDefinition`) and produces a regular `IntegrationFlow` lambda implementation.
See more overloaded `integrationFlow()` variants below.
Many other scenarios require an `IntegrationFlow` to be started from the source of data (e.g. `JdbcPollingChannelAdapter`, `JmsInboundGateway` or just an existing `MessageChannel`).
For this purpose, Spring Integration Java DSL provides an `IntegrationFlow` factory with a number of overloaded `from()` methods.
This factory can be used in groovy as well:
====
[source, groovy]
----
@Bean
flowFromSupplier() {
IntegrationFlow.fromSupplier({ 'bar' }) { e -> e.poller { p -> p.fixedDelay(10).maxMessagesPerPoll(1) } }
.channel({ c -> c.queue('fromSupplierQueue') } as Function)
.get()
}
----
====
But unfortunately not all `from()` methods are compatible with Groovy structures.
To solve this, Spring Integration provides a Groovy DSL factory around the `IntegrationFlows` factory.
It is implemented as a set of overloaded `integrationFlow()` functions.
With a consumer for a `GroovyIntegrationFlowDefinition` to declare the remainder of the flow as an `IntegrationFlow` closure to reuse the mentioned above experience and also avoid the need for a `get()` call in the end.
For example:
====
[source, groovy]
----
@Bean
functionFlow() {
integrationFlow Function<byte[], String>,
{ beanName 'functionGateway' },
{
transform Transformers.objectToString(), { id 'objectToStringTransformer' }
transform String, { it.toUpperCase() }
split Message<?>, { it.payload }
split Object, { it }, { id 'splitterEndpoint' }
resequence()
aggregate {
id 'aggregator'
outputProcessor { it.one }
}
}
}
@Bean
someFlow() {
integrationFlow ({ 'test' },
{
poller { it.trigger new OnlyOnceTrigger() }
id 'pollingSource'
})
{
log LoggingHandler.Level.WARN, 'test.category'
channel { queue 'pollerResultChannel' }
}
}
----
====

View File

@@ -1,5 +1,5 @@
[[groovy]]
=== Groovy support
=== Groovy Support
In Spring Integration 2.0, we added Groovy support, letting you use the Groovy scripting language to provide the logic for various integration components -- similar to the way the Spring Expression Language (SpEL) is supported for routing, transformation, and other integration concerns.
For more information about Groovy, see the Groovy documentation, which you can find on the https://groovy-lang.org/[project website].
@@ -23,6 +23,8 @@ compile "org.springframework.integration:spring-integration-groovy:{project-vers
----
====
In addition, starting with version 6.0, a <<./groovy-dsl.adoc#groovy-dsl,Groovy DSL>> for integration flow configurations is provided.
[[groovy-config]]
==== Groovy Configuration

View File

@@ -17,6 +17,9 @@ In general the project has been moved to Java 17 baseline and migrated from Java
[[x6.0-new-components]]
=== New Components
A Groovy DSL implementation for integration flow definitions has been added.
See <<./groovy-dsl.adoc#groovy-dsl,Groovy DSL>> for more information.
[[x6.0-mqtt]]
==== MQTT ClientManager