INT-2285 Implement max-subscribers on Channels

Add a subscriber limit on both unicast and publish-subscribe
channels. This permits detection of inadvertent channel wiring
during context initialization.

Also add a mechanism to globally set these defaults.

Two properties are added to ChannelInitializer. If the
'channelInitializer' bean is declared before a channel, and
these properties are set, it will globally override the
default (Integer.MAX_VALUE) for these properties.

For example:

    <bean id="channelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
        <property name="autoCreate" value="true" />
        <property name="defaultMaxUnicastSubscribers" value="1" />
        <property name="defaultMaxMulticastSubscribers" value="2" />
    </bean>

will make the default max-subscribers 1 and 2 for <channel/> and
<publish-subscribe/> channels respectively.

Also applies to module channels (jms, amqp, redis).
This commit is contained in:
Gary Russell
2012-07-10 18:06:16 -04:00
committed by Gunnar Hillert
parent a90a7e8e0b
commit 2ecb14de2a
35 changed files with 606 additions and 78 deletions

View File

@@ -20,7 +20,6 @@ import javax.jms.MessageListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.Message;
@@ -29,6 +28,7 @@ import org.springframework.integration.MessageDispatchingException;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.dispatcher.AbstractDispatcher;
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
import org.springframework.integration.dispatcher.MessageDispatcher;
import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy;
@@ -48,16 +48,26 @@ public class SubscribableJmsChannel extends AbstractJmsChannel implements Subscr
private final AbstractMessageListenerContainer container;
private volatile MessageDispatcher dispatcher;
private volatile AbstractDispatcher dispatcher;
private volatile boolean initialized;
private volatile int maxSubscribers = Integer.MAX_VALUE;
public SubscribableJmsChannel(AbstractMessageListenerContainer container, JmsTemplate jmsTemplate) {
super(jmsTemplate);
Assert.notNull(container, "container must not be null");
this.container = container;
}
/**
* Specify the maximum number of subscribers supported by the
* channel's dispatcher.
* @param maxSubscribers
*/
public void setMaxSubscribers(int maxSubscribers) {
this.maxSubscribers = maxSubscribers;
}
public boolean subscribe(MessageHandler handler) {
Assert.state(this.dispatcher != null, "'MessageDispatcher' must not be null. This channel might not have been initialized");
@@ -96,6 +106,7 @@ public class SubscribableJmsChannel extends AbstractJmsChannel implements Subscr
unicastingDispatcher.setLoadBalancingStrategy(new RoundRobinLoadBalancingStrategy());
this.dispatcher = unicastingDispatcher;
}
this.dispatcher.setMaxSubscribers(this.maxSubscribers);
}

View File

@@ -74,7 +74,7 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
private volatile String clientId;
private volatile String concurrency;
private volatile Integer concurrentConsumers;
private volatile ConnectionFactory connectionFactory;
@@ -110,7 +110,7 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
private volatile Long receiveTimeout;
private volatile Long recoveryInterval;
private volatile String beanName;
/**
@@ -133,6 +133,8 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
private volatile Integer transactionTimeout;
private volatile int maxSubscribers = Integer.MAX_VALUE;
public JmsChannelFactoryBean() {
this(true);
@@ -202,7 +204,7 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
public void setConcurrency(String concurrency) {
this.concurrency = concurrency;
}
public void setConcurrentConsumers(int concurrentConsumers) {
this.concurrentConsumers = concurrentConsumers;
}
@@ -314,6 +316,10 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
this.transactionTimeout = transactionTimeout;
}
public void setMaxSubscribers(int maxSubscribers) {
this.maxSubscribers = maxSubscribers;
}
public void setBeanName(String name) {
this.beanName = name;
}
@@ -328,7 +334,9 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
this.initializeJmsTemplate();
if (this.messageDriven) {
this.container = this.createContainer();
this.channel = new SubscribableJmsChannel(this.container, this.jmsTemplate);
SubscribableJmsChannel subscribableJmsChannel = new SubscribableJmsChannel(this.container, this.jmsTemplate);
subscribableJmsChannel.setMaxSubscribers(this.maxSubscribers);
this.channel = subscribableJmsChannel;
}
else {
Assert.isTrue(!Boolean.TRUE.equals(this.pubSubDomain),
@@ -388,27 +396,27 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
container.setSessionAcknowledgeMode(this.sessionAcknowledgeMode);
container.setSessionTransacted(this.sessionTransacted);
container.setSubscriptionDurable(this.subscriptionDurable);
if (container instanceof DefaultMessageListenerContainer) {
DefaultMessageListenerContainer dmlc = (DefaultMessageListenerContainer) container;
if (this.cacheLevelName != null) {
dmlc.setCacheLevelName(this.cacheLevelName);
}
if (StringUtils.hasText(this.concurrency)){
dmlc.setConcurrency(this.concurrency);
}
if (this.concurrentConsumers != null){
dmlc.setConcurrentConsumers(this.concurrentConsumers);
}
if (this.maxConcurrentConsumers != null){
dmlc.setMaxConcurrentConsumers(this.maxConcurrentConsumers);
}
if (this.idleTaskExecutionLimit != null) {
dmlc.setIdleTaskExecutionLimit(this.idleTaskExecutionLimit);
}
@@ -437,11 +445,11 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
if (StringUtils.hasText(this.concurrency)){
smlc.setConcurrency(this.concurrency);
}
if (this.concurrentConsumers != null){
smlc.setConcurrentConsumers(this.concurrentConsumers);
}
smlc.setPubSubNoLocal(this.pubSubNoLocal);
smlc.setTaskExecutor(this.taskExecutor);
}
@@ -485,6 +493,7 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
}
}
@Override
protected void destroyInstance(AbstractJmsChannel instance) throws Exception {
if (instance instanceof SubscribableJmsChannel) {
((SubscribableJmsChannel) this.channel).destroy();

View File

@@ -58,9 +58,11 @@ public class JmsChannelParser extends AbstractChannelParser {
builder.addPropertyReference("connectionFactory", connectionFactory);
if ("channel".equals(element.getLocalName())) {
this.parseDestination(element, parserContext, builder, "queue");
this.setMaxSubscribersProperty(parserContext, builder, element, IntegrationNamespaceUtils.DEFAULT_MAX_UNICAST_SUBSCRIBERS_PROPERTY_NAME);
}
else if ("publish-subscribe-channel".equals(element.getLocalName())) {
this.parseDestination(element, parserContext, builder, "topic");
this.setMaxSubscribersProperty(parserContext, builder, element, IntegrationNamespaceUtils.DEFAULT_MAX_BROADCAST_SUBSCRIBERS_PROPERTY_NAME);
}
String containerType = element.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
String containerClass = element.getAttribute(CONTAINER_CLASS_ATTRIBUTE);

View File

@@ -376,6 +376,7 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="integration:subscribersAttributeGroup" />
</xsd:complexType>
<xsd:element name="message-driven-channel-adapter">

View File

@@ -16,7 +16,7 @@
time-to-live="123"
priority="12"/>
<jms:channel id="queueNameChannel" queue-name="test.queue"/>
<jms:channel id="queueNameChannel" queue-name="test.queue" max-subscribers="1" />
<jms:channel id="queueNameWithResolverChannel" queue-name="foo"
destination-resolver="destinationResolver" connection-factory="connFact"/>

View File

@@ -131,6 +131,8 @@ public class JmsChannelParserTests {
assertEquals(DeliveryMode.PERSISTENT, TestUtils.getPropertyValue(jmsTemplate, "deliveryMode"));
assertEquals(123L, TestUtils.getPropertyValue(jmsTemplate, "timeToLive"));
assertEquals(12, TestUtils.getPropertyValue(jmsTemplate, "priority"));
assertEquals(Integer.MAX_VALUE, TestUtils.getPropertyValue(
TestUtils.getPropertyValue(channel, "dispatcher"), "maxSubscribers", Integer.class).intValue());
}
@Test
@@ -142,6 +144,8 @@ public class JmsChannelParserTests {
AbstractMessageListenerContainer container = (AbstractMessageListenerContainer) accessor.getPropertyValue("container");
assertEquals("test.queue", jmsTemplate.getDefaultDestinationName());
assertEquals("test.queue", container.getDestinationName());
assertEquals(1, TestUtils.getPropertyValue(
TestUtils.getPropertyValue(channel, "dispatcher"), "maxSubscribers", Integer.class).intValue());
}
@Test