GH-3192: pub-sub DSL for broker-backed channels (#3193)

* GH-3192: pub-sub DSL for broker-backed channels

Fixes https://github.com/spring-projects/spring-integration/issues/3192

* Introduce a `BroadcastCapableChannel` abstract to indicate those `SubscribableChannel`
implementations which can provide a pub-sub functionality
* Implement a `BroadcastCapableChannel` in broker-baked channels with pub-sub option
* Introduce a `BaseIntegrationFlowDefinition.publishSubscribeChannel()` based
on the `BroadcastCapableChannel` and `BroadcastPublishSubscribeSpec` to let to
configure sub-flow subscribers in fluent manner

* * Add some JavaDocs and document new feature

* * Show the channel bean definition in the doc
* Fix typo
This commit is contained in:
Artem Bilan
2020-02-25 13:01:38 -05:00
committed by GitHub
parent 7dbdbdee3f
commit 87c8e47a88
11 changed files with 265 additions and 63 deletions

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2020 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.channel;
import org.springframework.messaging.SubscribableChannel;
/**
* A {@link SubscribableChannel} variant for implementations with broadcasting capabilities.
*
* @author Artem Bilan
*
* @since 5.3
*/
public interface BroadcastCapableChannel extends SubscribableChannel {
/**
* Return a state of this channel in regards of broadcasting capabilities.
* @return the state of this channel in regards of broadcasting capabilities.
*/
default boolean isBroadcast() {
return true;
}
}

View File

@@ -35,7 +35,7 @@ import org.springframework.util.ErrorHandler;
* @author Gary Russell
* @author Artem Bilan
*/
public class PublishSubscribeChannel extends AbstractExecutorChannel {
public class PublishSubscribeChannel extends AbstractExecutorChannel implements BroadcastCapableChannel {
private ErrorHandler errorHandler;
@@ -45,6 +45,14 @@ public class PublishSubscribeChannel extends AbstractExecutorChannel {
private int minSubscribers;
/**
* Create a PublishSubscribeChannel that will invoke the handlers in the
* message sender's thread.
*/
public PublishSubscribeChannel() {
this(null);
}
/**
* Create a PublishSubscribeChannel that will use an {@link Executor}
* to invoke the handlers. If this is null, each invocation will occur in
@@ -56,14 +64,6 @@ public class PublishSubscribeChannel extends AbstractExecutorChannel {
this.dispatcher = new BroadcastingDispatcher(executor);
}
/**
* Create a PublishSubscribeChannel that will invoke the handlers in the
* message sender's thread.
*/
public PublishSubscribeChannel() {
this(null);
}
@Override
public String getComponentType() {
@@ -135,7 +135,7 @@ public class PublishSubscribeChannel extends AbstractExecutorChannel {
@Override
public final void onInit() {
super.onInit();
BeanFactory beanFactory = this.getBeanFactory();
BeanFactory beanFactory = getBeanFactory();
BroadcastingDispatcher dispatcherToUse = getDispatcher();
if (this.executor != null) {
Assert.state(dispatcherToUse.getHandlerCount() == 0,

View File

@@ -35,6 +35,7 @@ import org.springframework.beans.factory.config.DestructionAwareBeanPostProcesso
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.aggregator.AggregatingMessageHandler;
import org.springframework.integration.channel.BroadcastCapableChannel;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.FixedSubscriberChannel;
import org.springframework.integration.channel.FluxMessageChannel;
@@ -294,6 +295,25 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
return addComponents(spec.getComponentsToRegister()).channel(spec);
}
/**
* The {@link BroadcastCapableChannel} {@link #channel}
* method specific implementation to allow the use of the 'subflow' subscriber capability.
* @param broadcastCapableChannel the {@link BroadcastCapableChannel} to subscriber sub-flows to.
* @param publishSubscribeChannelConfigurer the {@link Consumer} to specify
* {@link BroadcastPublishSubscribeSpec} 'subflow' definitions.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @since 5.3
*/
public B publishSubscribeChannel(BroadcastCapableChannel broadcastCapableChannel,
Consumer<BroadcastPublishSubscribeSpec> publishSubscribeChannelConfigurer) {
Assert.notNull(publishSubscribeChannelConfigurer, "'publishSubscribeChannelConfigurer' must not be null");
BroadcastPublishSubscribeSpec spec = new BroadcastPublishSubscribeSpec(broadcastCapableChannel);
publishSubscribeChannelConfigurer.accept(spec);
return addComponents(spec.getComponentsToRegister())
.channel(broadcastCapableChannel);
}
/**
* Populate the {@code Wire Tap} EI Pattern specific
* {@link org.springframework.messaging.support.ChannelInterceptor} implementation

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2020 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.dsl;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.integration.channel.BroadcastCapableChannel;
import org.springframework.messaging.MessageChannel;
import org.springframework.util.Assert;
/**
* An {@link IntegrationComponentSpec} for configuring sub-flow subscribers on the
* provided {@link BroadcastCapableChannel}.
*
* @author Artem Bilan
* @author Gary Russell
*
* @since 5.3
*/
public class BroadcastPublishSubscribeSpec
extends IntegrationComponentSpec<BroadcastPublishSubscribeSpec, BroadcastCapableChannel>
implements ComponentsRegistration {
private final Map<Object, String> subscriberFlows = new LinkedHashMap<>();
private int order;
protected BroadcastPublishSubscribeSpec(BroadcastCapableChannel channel) {
Assert.state(channel.isBroadcast(),
() -> "the " + channel +
" must be in the 'broadcast' state for using from this 'BroadcastPublishSubscribeSpec'");
this.target = channel;
}
/**
* Configure a {@link IntegrationFlow} to configure as a subscriber
* for the current {@link BroadcastCapableChannel}.
* @param subFlow the {@link IntegrationFlow} to configure as a subscriber
* for the current {@link BroadcastCapableChannel}.
* @return the current spec
*/
public BroadcastPublishSubscribeSpec subscribe(IntegrationFlow subFlow) {
Assert.notNull(subFlow, "'subFlow' must not be null");
IntegrationFlowBuilder flowBuilder =
IntegrationFlows.from(this.target)
.bridge(consumer -> consumer.order(this.order++));
MessageChannel subFlowInput = subFlow.getInputChannel();
if (subFlowInput == null) {
subFlow.configure(flowBuilder);
}
else {
flowBuilder.channel(subFlowInput);
}
this.subscriberFlows.put(flowBuilder.get(), null);
return _this();
}
@Override
public Map<Object, String> getComponentsToRegister() {
return this.subscriberFlows;
}
}

View File

@@ -21,10 +21,11 @@ import java.util.Map;
import java.util.concurrent.Executor;
import org.springframework.lang.Nullable;
import org.springframework.messaging.MessageChannel;
import org.springframework.util.Assert;
/**
* The {@link PublishSubscribeChannelSpec} extension to configure as a general flow callback for sub-flows
* as subscribers.
*
* @author Artem Bilan
* @author Gary Russell
*
@@ -32,15 +33,15 @@ import org.springframework.util.Assert;
*/
public class PublishSubscribeSpec extends PublishSubscribeChannelSpec<PublishSubscribeSpec> {
private final Map<Object, String> subscriberFlows = new LinkedHashMap<>();
private int order;
private final BroadcastPublishSubscribeSpec delegate;
protected PublishSubscribeSpec() {
this.delegate = new BroadcastPublishSubscribeSpec(this.channel);
}
protected PublishSubscribeSpec(@Nullable Executor executor) {
super(executor);
this.delegate = new BroadcastPublishSubscribeSpec(this.channel);
}
@Override
@@ -49,21 +50,7 @@ public class PublishSubscribeSpec extends PublishSubscribeChannelSpec<PublishSub
}
public PublishSubscribeSpec subscribe(IntegrationFlow subFlow) {
Assert.notNull(subFlow, "'subFlow' must not be null");
IntegrationFlowBuilder flowBuilder =
IntegrationFlows.from(this.channel)
.bridge(consumer -> consumer.order(this.order++));
MessageChannel subFlowInput = subFlow.getInputChannel();
if (subFlowInput == null) {
subFlow.configure(flowBuilder);
}
else {
flowBuilder.channel(subFlowInput);
}
this.subscriberFlows.put(flowBuilder.get(), null);
this.delegate.subscribe(subFlow);
return _this();
}
@@ -71,7 +58,7 @@ public class PublishSubscribeSpec extends PublishSubscribeChannelSpec<PublishSub
public Map<Object, String> getComponentsToRegister() {
Map<Object, String> objects = new LinkedHashMap<>();
objects.putAll(super.getComponentsToRegister());
objects.putAll(this.subscriberFlows);
objects.putAll(this.delegate.getComponentsToRegister());
return objects;
}