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:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -25,22 +25,25 @@ import org.springframework.amqp.core.FanoutExchange;
|
|||||||
import org.springframework.amqp.core.Queue;
|
import org.springframework.amqp.core.Queue;
|
||||||
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
|
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
|
||||||
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
|
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
|
||||||
|
import org.springframework.integration.channel.BroadcastCapableChannel;
|
||||||
import org.springframework.integration.dispatcher.AbstractDispatcher;
|
import org.springframework.integration.dispatcher.AbstractDispatcher;
|
||||||
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
|
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The {@link AbstractSubscribableAmqpChannel} extension for pub-sub semantics based on the {@link FanoutExchange}.
|
||||||
|
*
|
||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
* @author Gary Russell
|
* @author Gary Russell
|
||||||
* @author Artem Bilan
|
* @author Artem Bilan
|
||||||
*
|
*
|
||||||
* @since 2.1
|
* @since 2.1
|
||||||
*/
|
*/
|
||||||
public class PublishSubscribeAmqpChannel extends AbstractSubscribableAmqpChannel {
|
public class PublishSubscribeAmqpChannel extends AbstractSubscribableAmqpChannel implements BroadcastCapableChannel {
|
||||||
|
|
||||||
private volatile FanoutExchange exchange;
|
|
||||||
|
|
||||||
private final Queue queue = new AnonymousQueue();
|
private final Queue queue = new AnonymousQueue();
|
||||||
|
|
||||||
|
private volatile FanoutExchange exchange;
|
||||||
|
|
||||||
private volatile Binding binding;
|
private volatile Binding binding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +56,7 @@ public class PublishSubscribeAmqpChannel extends AbstractSubscribableAmqpChannel
|
|||||||
*/
|
*/
|
||||||
public PublishSubscribeAmqpChannel(String channelName, AbstractMessageListenerContainer container,
|
public PublishSubscribeAmqpChannel(String channelName, AbstractMessageListenerContainer container,
|
||||||
AmqpTemplate amqpTemplate) {
|
AmqpTemplate amqpTemplate) {
|
||||||
|
|
||||||
super(channelName, container, amqpTemplate, true);
|
super(channelName, container, amqpTemplate, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +73,7 @@ public class PublishSubscribeAmqpChannel extends AbstractSubscribableAmqpChannel
|
|||||||
*/
|
*/
|
||||||
public PublishSubscribeAmqpChannel(String channelName, AbstractMessageListenerContainer container,
|
public PublishSubscribeAmqpChannel(String channelName, AbstractMessageListenerContainer container,
|
||||||
AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) {
|
AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) {
|
||||||
|
|
||||||
super(channelName, container, amqpTemplate, true, outboundMapper, inboundMapper);
|
super(channelName, container, amqpTemplate, true, outboundMapper, inboundMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +109,7 @@ public class PublishSubscribeAmqpChannel extends AbstractSubscribableAmqpChannel
|
|||||||
@Override
|
@Override
|
||||||
protected AbstractDispatcher createDispatcher() {
|
protected AbstractDispatcher createDispatcher() {
|
||||||
BroadcastingDispatcher broadcastingDispatcher = new BroadcastingDispatcher(true);
|
BroadcastingDispatcher broadcastingDispatcher = new BroadcastingDispatcher(true);
|
||||||
broadcastingDispatcher.setBeanFactory(this.getBeanFactory());
|
broadcastingDispatcher.setBeanFactory(getBeanFactory());
|
||||||
return broadcastingDispatcher;
|
return broadcastingDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ import org.springframework.util.ErrorHandler;
|
|||||||
* @author Gary Russell
|
* @author Gary Russell
|
||||||
* @author Artem Bilan
|
* @author Artem Bilan
|
||||||
*/
|
*/
|
||||||
public class PublishSubscribeChannel extends AbstractExecutorChannel {
|
public class PublishSubscribeChannel extends AbstractExecutorChannel implements BroadcastCapableChannel {
|
||||||
|
|
||||||
private ErrorHandler errorHandler;
|
private ErrorHandler errorHandler;
|
||||||
|
|
||||||
@@ -45,6 +45,14 @@ public class PublishSubscribeChannel extends AbstractExecutorChannel {
|
|||||||
|
|
||||||
private int minSubscribers;
|
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}
|
* Create a PublishSubscribeChannel that will use an {@link Executor}
|
||||||
* to invoke the handlers. If this is null, each invocation will occur in
|
* 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);
|
this.dispatcher = new BroadcastingDispatcher(executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a PublishSubscribeChannel that will invoke the handlers in the
|
|
||||||
* message sender's thread.
|
|
||||||
*/
|
|
||||||
public PublishSubscribeChannel() {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getComponentType() {
|
public String getComponentType() {
|
||||||
@@ -135,7 +135,7 @@ public class PublishSubscribeChannel extends AbstractExecutorChannel {
|
|||||||
@Override
|
@Override
|
||||||
public final void onInit() {
|
public final void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
BeanFactory beanFactory = this.getBeanFactory();
|
BeanFactory beanFactory = getBeanFactory();
|
||||||
BroadcastingDispatcher dispatcherToUse = getDispatcher();
|
BroadcastingDispatcher dispatcherToUse = getDispatcher();
|
||||||
if (this.executor != null) {
|
if (this.executor != null) {
|
||||||
Assert.state(dispatcherToUse.getHandlerCount() == 0,
|
Assert.state(dispatcherToUse.getHandlerCount() == 0,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import org.springframework.beans.factory.config.DestructionAwareBeanPostProcesso
|
|||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.integration.aggregator.AggregatingMessageHandler;
|
import org.springframework.integration.aggregator.AggregatingMessageHandler;
|
||||||
|
import org.springframework.integration.channel.BroadcastCapableChannel;
|
||||||
import org.springframework.integration.channel.DirectChannel;
|
import org.springframework.integration.channel.DirectChannel;
|
||||||
import org.springframework.integration.channel.FixedSubscriberChannel;
|
import org.springframework.integration.channel.FixedSubscriberChannel;
|
||||||
import org.springframework.integration.channel.FluxMessageChannel;
|
import org.springframework.integration.channel.FluxMessageChannel;
|
||||||
@@ -294,6 +295,25 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
|
|||||||
return addComponents(spec.getComponentsToRegister()).channel(spec);
|
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
|
* Populate the {@code Wire Tap} EI Pattern specific
|
||||||
* {@link org.springframework.messaging.support.ChannelInterceptor} implementation
|
* {@link org.springframework.messaging.support.ChannelInterceptor} implementation
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,10 +21,11 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
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 Artem Bilan
|
||||||
* @author Gary Russell
|
* @author Gary Russell
|
||||||
*
|
*
|
||||||
@@ -32,15 +33,15 @@ import org.springframework.util.Assert;
|
|||||||
*/
|
*/
|
||||||
public class PublishSubscribeSpec extends PublishSubscribeChannelSpec<PublishSubscribeSpec> {
|
public class PublishSubscribeSpec extends PublishSubscribeChannelSpec<PublishSubscribeSpec> {
|
||||||
|
|
||||||
private final Map<Object, String> subscriberFlows = new LinkedHashMap<>();
|
private final BroadcastPublishSubscribeSpec delegate;
|
||||||
|
|
||||||
private int order;
|
|
||||||
|
|
||||||
protected PublishSubscribeSpec() {
|
protected PublishSubscribeSpec() {
|
||||||
|
this.delegate = new BroadcastPublishSubscribeSpec(this.channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PublishSubscribeSpec(@Nullable Executor executor) {
|
protected PublishSubscribeSpec(@Nullable Executor executor) {
|
||||||
super(executor);
|
super(executor);
|
||||||
|
this.delegate = new BroadcastPublishSubscribeSpec(this.channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,21 +50,7 @@ public class PublishSubscribeSpec extends PublishSubscribeChannelSpec<PublishSub
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PublishSubscribeSpec subscribe(IntegrationFlow subFlow) {
|
public PublishSubscribeSpec subscribe(IntegrationFlow subFlow) {
|
||||||
Assert.notNull(subFlow, "'subFlow' must not be null");
|
this.delegate.subscribe(subFlow);
|
||||||
|
|
||||||
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);
|
|
||||||
return _this();
|
return _this();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +58,7 @@ public class PublishSubscribeSpec extends PublishSubscribeChannelSpec<PublishSub
|
|||||||
public Map<Object, String> getComponentsToRegister() {
|
public Map<Object, String> getComponentsToRegister() {
|
||||||
Map<Object, String> objects = new LinkedHashMap<>();
|
Map<Object, String> objects = new LinkedHashMap<>();
|
||||||
objects.putAll(super.getComponentsToRegister());
|
objects.putAll(super.getComponentsToRegister());
|
||||||
objects.putAll(this.subscriberFlows);
|
objects.putAll(this.delegate.getComponentsToRegister());
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
import org.springframework.context.SmartLifecycle;
|
import org.springframework.context.SmartLifecycle;
|
||||||
import org.springframework.integration.MessageDispatchingException;
|
import org.springframework.integration.MessageDispatchingException;
|
||||||
|
import org.springframework.integration.channel.BroadcastCapableChannel;
|
||||||
import org.springframework.integration.context.IntegrationProperties;
|
import org.springframework.integration.context.IntegrationProperties;
|
||||||
import org.springframework.integration.dispatcher.AbstractDispatcher;
|
import org.springframework.integration.dispatcher.AbstractDispatcher;
|
||||||
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
|
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
|
||||||
@@ -37,10 +38,13 @@ import org.springframework.messaging.Message;
|
|||||||
import org.springframework.messaging.MessageDeliveryException;
|
import org.springframework.messaging.MessageDeliveryException;
|
||||||
import org.springframework.messaging.MessageHandler;
|
import org.springframework.messaging.MessageHandler;
|
||||||
import org.springframework.messaging.MessagingException;
|
import org.springframework.messaging.MessagingException;
|
||||||
import org.springframework.messaging.SubscribableChannel;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An {@link AbstractJmsChannel} implementation for message-driven subscriptions.
|
||||||
|
* Also implements a {@link BroadcastCapableChannel} to represent possible pub-sub semantics
|
||||||
|
* when configured against JMS topic.
|
||||||
|
*
|
||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
* @author Gary Russell
|
* @author Gary Russell
|
||||||
* @author Artem Bilan
|
* @author Artem Bilan
|
||||||
@@ -48,7 +52,7 @@ import org.springframework.util.Assert;
|
|||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public class SubscribableJmsChannel extends AbstractJmsChannel
|
public class SubscribableJmsChannel extends AbstractJmsChannel
|
||||||
implements SubscribableChannel, SmartLifecycle {
|
implements BroadcastCapableChannel, SmartLifecycle {
|
||||||
|
|
||||||
private final AbstractMessageListenerContainer container;
|
private final AbstractMessageListenerContainer container;
|
||||||
|
|
||||||
@@ -87,17 +91,22 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
|
|||||||
return this.dispatcher.removeHandler(handler);
|
return this.dispatcher.removeHandler(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBroadcast() {
|
||||||
|
return this.container.isPubSubDomain();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInit() {
|
public void onInit() {
|
||||||
if (this.initialized) {
|
if (this.initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
super.onInit();
|
super.onInit();
|
||||||
boolean isPubSub = this.container.isPubSubDomain();
|
boolean isPubSub = isBroadcast();
|
||||||
this.configureDispatcher(isPubSub);
|
configureDispatcher(isPubSub);
|
||||||
MessageListener listener = new DispatchingMessageListener(
|
MessageListener listener =
|
||||||
this.getJmsTemplate(), this.dispatcher,
|
new DispatchingMessageListener(getJmsTemplate(), this.dispatcher, this, isPubSub,
|
||||||
this, isPubSub, this.getMessageBuilderFactory());
|
getMessageBuilderFactory());
|
||||||
this.container.setMessageListener(listener);
|
this.container.setMessageListener(listener);
|
||||||
if (!this.container.isActive()) {
|
if (!this.container.isActive()) {
|
||||||
this.container.afterPropertiesSet();
|
this.container.afterPropertiesSet();
|
||||||
@@ -108,7 +117,7 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
|
|||||||
private void configureDispatcher(boolean isPubSub) {
|
private void configureDispatcher(boolean isPubSub) {
|
||||||
if (isPubSub) {
|
if (isPubSub) {
|
||||||
BroadcastingDispatcher broadcastingDispatcher = new BroadcastingDispatcher(true);
|
BroadcastingDispatcher broadcastingDispatcher = new BroadcastingDispatcher(true);
|
||||||
broadcastingDispatcher.setBeanFactory(this.getBeanFactory());
|
broadcastingDispatcher.setBeanFactory(getBeanFactory());
|
||||||
this.dispatcher = broadcastingDispatcher;
|
this.dispatcher = broadcastingDispatcher;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -118,8 +127,8 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
|
|||||||
}
|
}
|
||||||
if (this.maxSubscribers == null) {
|
if (this.maxSubscribers == null) {
|
||||||
this.maxSubscribers = this.getIntegrationProperty(isPubSub ?
|
this.maxSubscribers = this.getIntegrationProperty(isPubSub ?
|
||||||
IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS :
|
IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS :
|
||||||
IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS,
|
IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS,
|
||||||
Integer.class);
|
Integer.class);
|
||||||
}
|
}
|
||||||
this.dispatcher.setMaxSubscribers(this.maxSubscribers);
|
this.dispatcher.setMaxSubscribers(this.maxSubscribers);
|
||||||
@@ -194,6 +203,7 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
|
|||||||
DispatchingMessageListener(JmsTemplate jmsTemplate,
|
DispatchingMessageListener(JmsTemplate jmsTemplate,
|
||||||
MessageDispatcher dispatcher, SubscribableJmsChannel channel, boolean isPubSub,
|
MessageDispatcher dispatcher, SubscribableJmsChannel channel, boolean isPubSub,
|
||||||
MessageBuilderFactory messageBuilderFactory) {
|
MessageBuilderFactory messageBuilderFactory) {
|
||||||
|
|
||||||
this.jmsTemplate = jmsTemplate;
|
this.jmsTemplate = jmsTemplate;
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = dispatcher;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
@@ -212,8 +222,10 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
|
|||||||
converted = converter.fromMessage(message);
|
converted = converter.fromMessage(message);
|
||||||
}
|
}
|
||||||
if (converted != null) {
|
if (converted != null) {
|
||||||
messageToSend = (converted instanceof Message<?>) ? (Message<?>) converted
|
messageToSend =
|
||||||
: this.messageBuilderFactory.withPayload(converted).build();
|
converted instanceof Message<?>
|
||||||
|
? (Message<?>) converted
|
||||||
|
: this.messageBuilderFactory.withPayload(converted).build();
|
||||||
this.dispatcher.dispatch(messageToSend);
|
this.dispatcher.dispatch(messageToSend);
|
||||||
}
|
}
|
||||||
else if (this.logger.isWarnEnabled()) {
|
else if (this.logger.isWarnEnabled()) {
|
||||||
@@ -231,8 +243,7 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new MessageDeliveryException(
|
throw new MessageDeliveryException(messageToSend, exceptionMessage, e);
|
||||||
messageToSend, exceptionMessage, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2016-2019 the original author or authors.
|
* Copyright 2016-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -41,6 +41,7 @@ import org.springframework.integration.annotation.InboundChannelAdapter;
|
|||||||
import org.springframework.integration.annotation.IntegrationComponentScan;
|
import org.springframework.integration.annotation.IntegrationComponentScan;
|
||||||
import org.springframework.integration.annotation.MessagingGateway;
|
import org.springframework.integration.annotation.MessagingGateway;
|
||||||
import org.springframework.integration.annotation.Poller;
|
import org.springframework.integration.annotation.Poller;
|
||||||
|
import org.springframework.integration.channel.BroadcastCapableChannel;
|
||||||
import org.springframework.integration.channel.DirectChannel;
|
import org.springframework.integration.channel.DirectChannel;
|
||||||
import org.springframework.integration.channel.FixedSubscriberChannel;
|
import org.springframework.integration.channel.FixedSubscriberChannel;
|
||||||
import org.springframework.integration.channel.QueueChannel;
|
import org.springframework.integration.channel.QueueChannel;
|
||||||
@@ -125,6 +126,9 @@ public class JmsTests extends ActiveMQMultiContextTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PollableChannel jmsPubSubBridgeChannel;
|
private PollableChannel jmsPubSubBridgeChannel;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PollableChannel jmsPubSubBridgeChannel2;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("jmsOutboundGateway.handler")
|
@Qualifier("jmsOutboundGateway.handler")
|
||||||
private MessageHandler jmsOutboundGatewayHandler;
|
private MessageHandler jmsOutboundGatewayHandler;
|
||||||
@@ -254,6 +258,11 @@ public class JmsTests extends ActiveMQMultiContextTests {
|
|||||||
.isNotNull()
|
.isNotNull()
|
||||||
.extracting(Message::getPayload)
|
.extracting(Message::getPayload)
|
||||||
.isEqualTo("foo");
|
.isEqualTo("foo");
|
||||||
|
received = this.jmsPubSubBridgeChannel2.receive(5000);
|
||||||
|
assertThat(received)
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(Message::getPayload)
|
||||||
|
.isEqualTo("foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -351,10 +360,20 @@ public class JmsTests extends ActiveMQMultiContextTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IntegrationFlow pubSubFlow() {
|
public IntegrationFlow pubSubFlow() {
|
||||||
return IntegrationFlows
|
return f -> f
|
||||||
.from(Jms.publishSubscribeChannel(jmsConnectionFactory())
|
.publishSubscribeChannel(jmsPublishSubscribeChannel(),
|
||||||
.destination("pubsub"))
|
pubsub -> pubsub
|
||||||
.channel(c -> c.queue("jmsPubSubBridgeChannel"))
|
.subscribe(subFlow -> subFlow
|
||||||
|
.channel(c -> c.queue("jmsPubSubBridgeChannel")))
|
||||||
|
.subscribe(subFlow -> subFlow
|
||||||
|
.channel(c -> c.queue("jmsPubSubBridgeChannel2"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public BroadcastCapableChannel jmsPublishSubscribeChannel() {
|
||||||
|
// TODO reconsider target generic type for channel implementation to return from this kind of specs
|
||||||
|
return (BroadcastCapableChannel) Jms.publishSubscribeChannel(jmsConnectionFactory())
|
||||||
|
.destination("pubsub")
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -32,6 +32,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
|||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
import org.springframework.integration.MessageDispatchingException;
|
import org.springframework.integration.MessageDispatchingException;
|
||||||
import org.springframework.integration.channel.AbstractMessageChannel;
|
import org.springframework.integration.channel.AbstractMessageChannel;
|
||||||
|
import org.springframework.integration.channel.BroadcastCapableChannel;
|
||||||
import org.springframework.integration.channel.ChannelUtils;
|
import org.springframework.integration.channel.ChannelUtils;
|
||||||
import org.springframework.integration.context.IntegrationProperties;
|
import org.springframework.integration.context.IntegrationProperties;
|
||||||
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
|
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
|
||||||
@@ -40,13 +41,15 @@ import org.springframework.integration.util.ErrorHandlingTaskExecutor;
|
|||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.MessageDeliveryException;
|
import org.springframework.messaging.MessageDeliveryException;
|
||||||
import org.springframework.messaging.MessageHandler;
|
import org.springframework.messaging.MessageHandler;
|
||||||
import org.springframework.messaging.SubscribableChannel;
|
|
||||||
import org.springframework.messaging.converter.MessageConverter;
|
import org.springframework.messaging.converter.MessageConverter;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ErrorHandler;
|
import org.springframework.util.ErrorHandler;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An {@link AbstractMessageChannel} implementation with {@link BroadcastCapableChannel}
|
||||||
|
* aspect to provide a pub-sub semantics to consume messages fgrom Redis topic.
|
||||||
|
*
|
||||||
* @author Oleg Zhurakousky
|
* @author Oleg Zhurakousky
|
||||||
* @author Gary Russell
|
* @author Gary Russell
|
||||||
* @author Artem Bilan
|
* @author Artem Bilan
|
||||||
@@ -55,7 +58,7 @@ import org.springframework.util.StringUtils;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public class SubscribableRedisChannel extends AbstractMessageChannel
|
public class SubscribableRedisChannel extends AbstractMessageChannel
|
||||||
implements SubscribableChannel, SmartLifecycle {
|
implements BroadcastCapableChannel, SmartLifecycle {
|
||||||
|
|
||||||
private final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
private final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||||
|
|
||||||
@@ -67,10 +70,6 @@ public class SubscribableRedisChannel extends AbstractMessageChannel
|
|||||||
|
|
||||||
private final BroadcastingDispatcher dispatcher = new BroadcastingDispatcher(true);
|
private final BroadcastingDispatcher dispatcher = new BroadcastingDispatcher(true);
|
||||||
|
|
||||||
private volatile Integer maxSubscribers;
|
|
||||||
|
|
||||||
private volatile boolean initialized;
|
|
||||||
|
|
||||||
// defaults
|
// defaults
|
||||||
private Executor taskExecutor = new SimpleAsyncTaskExecutor();
|
private Executor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||||
|
|
||||||
@@ -78,6 +77,10 @@ public class SubscribableRedisChannel extends AbstractMessageChannel
|
|||||||
|
|
||||||
private MessageConverter messageConverter = new SimpleMessageConverter();
|
private MessageConverter messageConverter = new SimpleMessageConverter();
|
||||||
|
|
||||||
|
private volatile Integer maxSubscribers;
|
||||||
|
|
||||||
|
private volatile boolean initialized;
|
||||||
|
|
||||||
public SubscribableRedisChannel(RedisConnectionFactory connectionFactory, String topicName) {
|
public SubscribableRedisChannel(RedisConnectionFactory connectionFactory, String topicName) {
|
||||||
Assert.notNull(connectionFactory, "'connectionFactory' must not be null");
|
Assert.notNull(connectionFactory, "'connectionFactory' must not be null");
|
||||||
Assert.hasText(topicName, "'topicName' must not be empty");
|
Assert.hasText(topicName, "'topicName' must not be empty");
|
||||||
|
|||||||
@@ -742,6 +742,40 @@ public IntegrationFlow subscribersFlow() {
|
|||||||
You can achieve the same result with separate `IntegrationFlow` `@Bean` definitions, but we hope you find the sub-flow style of logic composition useful.
|
You can achieve the same result with separate `IntegrationFlow` `@Bean` definitions, but we hope you find the sub-flow style of logic composition useful.
|
||||||
We find that it results in shorter (and so more readable) code.
|
We find that it results in shorter (and so more readable) code.
|
||||||
|
|
||||||
|
Starting with version 5.3, a `BroadcastCapableChannel`-based `publishSubscribeChannel()` implementation is provided to configure sub-flow subscribers on broker-backed message channels.
|
||||||
|
For example we now can configure several subscribers as sub-flows on the `Jms.publishSubscribeChannel()`:
|
||||||
|
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
public BroadcastCapableChannel jmsPublishSubscribeChannel() {
|
||||||
|
return Jms.publishSubscribeChannel(jmsConnectionFactory())
|
||||||
|
.destination("pubsub")
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IntegrationFlow pubSubFlow() {
|
||||||
|
return f -> f
|
||||||
|
.publishSubscribeChannel(jmsPublishSubscribeChannel(),
|
||||||
|
pubsub -> pubsub
|
||||||
|
.subscribe(subFlow -> subFlow
|
||||||
|
.channel(c -> c.queue("jmsPubSubBridgeChannel1")))
|
||||||
|
.subscribe(subFlow -> subFlow
|
||||||
|
.channel(c -> c.queue("jmsPubSubBridgeChannel2"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public BroadcastCapableChannel jmsPublishSubscribeChannel(ConnectionFactory jmsConnectionFactory) {
|
||||||
|
return (BroadcastCapableChannel) Jms.publishSubscribeChannel(jmsConnectionFactory)
|
||||||
|
.destination("pubsub")
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
A similar `publish-subscribe` sub-flow composition provides the `.routeToRecipients()` method.
|
A similar `publish-subscribe` sub-flow composition provides the `.routeToRecipients()` method.
|
||||||
|
|
||||||
Another example is using `.discardFlow()` instead of `.discardChannel()` on the `.filter()` method.
|
Another example is using `.discardFlow()` instead of `.discardChannel()` on the `.filter()` method.
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ See <<./graph.adoc#integration-graph,Integration Graph>> for more information.
|
|||||||
In the aggregator, when the `MessageGroupProcessor` returns a `Message`, the `MessageBuilder.popSequenceDetails()` is performed on the output message if the `sequenceDetails` matches the header in the first message of the group.
|
In the aggregator, when the `MessageGroupProcessor` returns a `Message`, the `MessageBuilder.popSequenceDetails()` is performed on the output message if the `sequenceDetails` matches the header in the first message of the group.
|
||||||
See <<./aggregator.adoc#aggregator-api,Aggregator Programming Model>> for more information.
|
See <<./aggregator.adoc#aggregator-api,Aggregator Programming Model>> for more information.
|
||||||
|
|
||||||
|
A new `publishSubscribeChannel()` operator, based on the `BroadcastCapableChannel` and `BroadcastPublishSubscribeSpec`, was added into Java DSL.
|
||||||
|
This fluent API has its advantage when we configure sub-flows as pub-sub subscribers for broker-backed channels like `SubscribableJmsChannel`, `SubscribableRedisChannel` etc.
|
||||||
|
See <<./dsl.adoc#java-dsl-subflows,Sub-flows support>> for more information.
|
||||||
|
|
||||||
[[x5.3-amqp]]
|
[[x5.3-amqp]]
|
||||||
=== AMQP Changes
|
=== AMQP Changes
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user