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

@@ -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");
* 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.integration.MessageDispatchingException;
import org.springframework.integration.channel.BroadcastCapableChannel;
import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.dispatcher.AbstractDispatcher;
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
@@ -37,10 +38,13 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
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 Gary Russell
* @author Artem Bilan
@@ -48,7 +52,7 @@ import org.springframework.util.Assert;
* @since 2.0
*/
public class SubscribableJmsChannel extends AbstractJmsChannel
implements SubscribableChannel, SmartLifecycle {
implements BroadcastCapableChannel, SmartLifecycle {
private final AbstractMessageListenerContainer container;
@@ -87,17 +91,22 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
return this.dispatcher.removeHandler(handler);
}
@Override
public boolean isBroadcast() {
return this.container.isPubSubDomain();
}
@Override
public void onInit() {
if (this.initialized) {
return;
}
super.onInit();
boolean isPubSub = this.container.isPubSubDomain();
this.configureDispatcher(isPubSub);
MessageListener listener = new DispatchingMessageListener(
this.getJmsTemplate(), this.dispatcher,
this, isPubSub, this.getMessageBuilderFactory());
boolean isPubSub = isBroadcast();
configureDispatcher(isPubSub);
MessageListener listener =
new DispatchingMessageListener(getJmsTemplate(), this.dispatcher, this, isPubSub,
getMessageBuilderFactory());
this.container.setMessageListener(listener);
if (!this.container.isActive()) {
this.container.afterPropertiesSet();
@@ -108,7 +117,7 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
private void configureDispatcher(boolean isPubSub) {
if (isPubSub) {
BroadcastingDispatcher broadcastingDispatcher = new BroadcastingDispatcher(true);
broadcastingDispatcher.setBeanFactory(this.getBeanFactory());
broadcastingDispatcher.setBeanFactory(getBeanFactory());
this.dispatcher = broadcastingDispatcher;
}
else {
@@ -118,8 +127,8 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
}
if (this.maxSubscribers == null) {
this.maxSubscribers = this.getIntegrationProperty(isPubSub ?
IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS :
IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS,
IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS :
IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS,
Integer.class);
}
this.dispatcher.setMaxSubscribers(this.maxSubscribers);
@@ -194,6 +203,7 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
DispatchingMessageListener(JmsTemplate jmsTemplate,
MessageDispatcher dispatcher, SubscribableJmsChannel channel, boolean isPubSub,
MessageBuilderFactory messageBuilderFactory) {
this.jmsTemplate = jmsTemplate;
this.dispatcher = dispatcher;
this.channel = channel;
@@ -212,8 +222,10 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
converted = converter.fromMessage(message);
}
if (converted != null) {
messageToSend = (converted instanceof Message<?>) ? (Message<?>) converted
: this.messageBuilderFactory.withPayload(converted).build();
messageToSend =
converted instanceof Message<?>
? (Message<?>) converted
: this.messageBuilderFactory.withPayload(converted).build();
this.dispatcher.dispatch(messageToSend);
}
else if (this.logger.isWarnEnabled()) {
@@ -231,8 +243,7 @@ public class SubscribableJmsChannel extends AbstractJmsChannel
}
}
else {
throw new MessageDeliveryException(
messageToSend, exceptionMessage, e);
throw new MessageDeliveryException(messageToSend, exceptionMessage, e);
}
}
catch (Exception e) {

View File

@@ -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");
* 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.MessagingGateway;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.channel.BroadcastCapableChannel;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.FixedSubscriberChannel;
import org.springframework.integration.channel.QueueChannel;
@@ -125,6 +126,9 @@ public class JmsTests extends ActiveMQMultiContextTests {
@Autowired
private PollableChannel jmsPubSubBridgeChannel;
@Autowired
private PollableChannel jmsPubSubBridgeChannel2;
@Autowired
@Qualifier("jmsOutboundGateway.handler")
private MessageHandler jmsOutboundGatewayHandler;
@@ -254,6 +258,11 @@ public class JmsTests extends ActiveMQMultiContextTests {
.isNotNull()
.extracting(Message::getPayload)
.isEqualTo("foo");
received = this.jmsPubSubBridgeChannel2.receive(5000);
assertThat(received)
.isNotNull()
.extracting(Message::getPayload)
.isEqualTo("foo");
}
@Test
@@ -351,10 +360,20 @@ public class JmsTests extends ActiveMQMultiContextTests {
@Bean
public IntegrationFlow pubSubFlow() {
return IntegrationFlows
.from(Jms.publishSubscribeChannel(jmsConnectionFactory())
.destination("pubsub"))
.channel(c -> c.queue("jmsPubSubBridgeChannel"))
return f -> f
.publishSubscribeChannel(jmsPublishSubscribeChannel(),
pubsub -> pubsub
.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();
}