INT-4136: Support DirectMessageListenerContainer

JIRA: https://jira.spring.io/browse/INT-4136

Fix a couple typos
This commit is contained in:
Gary Russell
2016-10-08 13:28:53 -04:00
committed by Artem Bilan
parent 53d80b5694
commit 2edf766e49
13 changed files with 146 additions and 51 deletions

View File

@@ -27,7 +27,7 @@ import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.beans.factory.DisposableBean;
@@ -57,7 +57,7 @@ abstract class AbstractSubscribableAmqpChannel extends AbstractAmqpChannel
private final String channelName;
private final SimpleMessageListenerContainer container;
private final AbstractMessageListenerContainer container;
private volatile AbstractDispatcher dispatcher;
@@ -77,7 +77,7 @@ abstract class AbstractSubscribableAmqpChannel extends AbstractAmqpChannel
* @param amqpTemplate the template.
* @see #setExtractPayload(boolean)
*/
protected AbstractSubscribableAmqpChannel(String channelName, SimpleMessageListenerContainer container,
protected AbstractSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate) {
this(channelName, container, amqpTemplate, false);
}
@@ -93,7 +93,7 @@ abstract class AbstractSubscribableAmqpChannel extends AbstractAmqpChannel
* @see #setExtractPayload(boolean)
* @since 4.3
*/
protected AbstractSubscribableAmqpChannel(String channelName, SimpleMessageListenerContainer container,
protected AbstractSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) {
this(channelName, container, amqpTemplate, false, outboundMapper, inboundMapper);
}
@@ -108,7 +108,7 @@ abstract class AbstractSubscribableAmqpChannel extends AbstractAmqpChannel
* @see #setExtractPayload(boolean)
*/
protected AbstractSubscribableAmqpChannel(String channelName,
SimpleMessageListenerContainer container,
AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate, boolean isPubSub) {
super(amqpTemplate);
Assert.notNull(container, "container must not be null");
@@ -133,7 +133,7 @@ abstract class AbstractSubscribableAmqpChannel extends AbstractAmqpChannel
* @since 4.3
*/
protected AbstractSubscribableAmqpChannel(String channelName,
SimpleMessageListenerContainer container,
AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate, boolean isPubSub,
AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) {
super(amqpTemplate, outboundMapper, inboundMapper);
@@ -312,7 +312,7 @@ abstract class AbstractSubscribableAmqpChannel extends AbstractAmqpChannel
}
}
catch (Exception e) {
throw new MessagingException("Failure occured in AMQP listener " +
throw new MessagingException("Failure occurred in AMQP listener " +
"while attempting to convert and dispatch Message.", e);
}
}

View File

@@ -19,7 +19,7 @@ package org.springframework.integration.amqp.channel;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
import org.springframework.integration.dispatcher.AbstractDispatcher;
import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy;
@@ -43,7 +43,7 @@ public class PointToPointSubscribableAmqpChannel extends AbstractSubscribableAmq
* @param amqpTemplate the template.
* @see #setExtractPayload(boolean)
*/
public PointToPointSubscribableAmqpChannel(String channelName, SimpleMessageListenerContainer container,
public PointToPointSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate) {
super(channelName, container, amqpTemplate);
}
@@ -60,7 +60,7 @@ public class PointToPointSubscribableAmqpChannel extends AbstractSubscribableAmq
* @see #setExtractPayload(boolean)
* @since 4.3
*/
public PointToPointSubscribableAmqpChannel(String channelName, SimpleMessageListenerContainer container,
public PointToPointSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) {
super(channelName, container, amqpTemplate, outboundMapper, inboundMapper);
}

View File

@@ -27,7 +27,7 @@ import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
import org.springframework.integration.dispatcher.AbstractDispatcher;
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
@@ -55,7 +55,7 @@ public class PublishSubscribeAmqpChannel extends AbstractSubscribableAmqpChannel
* @param amqpTemplate the template.
* @see #setExtractPayload(boolean)
*/
public PublishSubscribeAmqpChannel(String channelName, SimpleMessageListenerContainer container,
public PublishSubscribeAmqpChannel(String channelName, AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate) {
super(channelName, container, amqpTemplate, true);
}
@@ -71,7 +71,7 @@ public class PublishSubscribeAmqpChannel extends AbstractSubscribableAmqpChannel
* @see #setExtractPayload(boolean)
* @since 4.3
*/
public PublishSubscribeAmqpChannel(String channelName, SimpleMessageListenerContainer container,
public PublishSubscribeAmqpChannel(String channelName, AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) {
super(channelName, container, amqpTemplate, true, outboundMapper, inboundMapper);
}

View File

@@ -22,6 +22,8 @@ import java.util.List;
import org.w3c.dom.Element;
import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
@@ -46,6 +48,7 @@ abstract class AbstractAmqpInboundAdapterParser extends AbstractSingleBeanDefini
"acknowledge-mode",
"channel-transacted",
"concurrent-consumers",
"consumers-per-queue",
"expose-listener-channel",
"phase",
"prefetch-count",
@@ -124,8 +127,26 @@ abstract class AbstractAmqpInboundAdapterParser extends AbstractSingleBeanDefini
parserContext.getReaderContext().error("If no 'listener-container' reference is provided, " +
"the 'queue-names' attribute is required.", element);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer");
String consumersPerQueue = element.getAttribute("consumers-per-queue");
BeanDefinitionBuilder builder;
if (StringUtils.hasText(consumersPerQueue)) {
builder = BeanDefinitionBuilder.genericBeanDefinition(DirectMessageListenerContainer.class);
if (StringUtils.hasText(element.getAttribute("concurrent-consumers"))) {
parserContext.getReaderContext().error("'consumers-per-queue' and 'concurrent-consumers' are mutually "
+ "exclusive", element);
}
if (StringUtils.hasText(element.getAttribute("tx-size"))) {
parserContext.getReaderContext().error("'tx-size' is not allowed with 'consumers-per-queue'", element);
}
if (StringUtils.hasText(element.getAttribute("receive-timeout"))) {
parserContext.getReaderContext().error("'receive-timeout' is not allowed with 'consumers-per-queue'",
element);
}
builder.addPropertyValue("consumersPerQueue", consumersPerQueue);
}
else {
builder = BeanDefinitionBuilder.genericBeanDefinition(SimpleMessageListenerContainer.class);
}
String connectionFactoryRef = element.getAttribute("connection-factory");
if (!StringUtils.hasText(connectionFactoryRef)) {
connectionFactoryRef = "rabbitConnectionFactory";

View File

@@ -29,6 +29,8 @@ import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import org.springframework.amqp.support.converter.MessageConverter;
@@ -89,6 +91,8 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
private volatile Integer concurrentConsumers;
private volatile Integer consumersPerQueue;
private volatile ConnectionFactory connectionFactory;
private volatile MessagePropertiesConverter messagePropertiesConverter;
@@ -261,6 +265,10 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
this.concurrentConsumers = concurrentConsumers;
}
public void setConsumersPerQueue(Integer consumersPerQueue) {
this.consumersPerQueue = consumersPerQueue;
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
@@ -341,7 +349,7 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
@Override
protected AbstractAmqpChannel createInstance() throws Exception {
if (this.messageDriven) {
SimpleMessageListenerContainer container = this.createContainer();
AbstractMessageListenerContainer container = this.createContainer();
if (this.amqpTemplate instanceof InitializingBean) {
((InitializingBean) this.amqpTemplate).afterPropertiesSet();
}
@@ -397,8 +405,26 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
return this.channel;
}
private SimpleMessageListenerContainer createContainer() throws Exception {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
private AbstractMessageListenerContainer createContainer() throws Exception {
AbstractMessageListenerContainer container;
if (this.consumersPerQueue == null) {
SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
if (this.concurrentConsumers != null) {
smlc.setConcurrentConsumers(this.concurrentConsumers);
}
if (this.receiveTimeout != null) {
smlc.setReceiveTimeout(this.receiveTimeout);
}
if (this.txSize != null) {
smlc.setTxSize(this.txSize);
}
container = smlc;
}
else {
DirectMessageListenerContainer dmlc = new DirectMessageListenerContainer();
dmlc.setConsumersPerQueue(this.consumersPerQueue);
container = dmlc;
}
if (this.acknowledgeMode != null) {
container.setAcknowledgeMode(this.acknowledgeMode);
}
@@ -407,9 +433,6 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
}
container.setAutoStartup(this.autoStartup);
container.setChannelTransacted(this.channelTransacted);
if (this.concurrentConsumers != null) {
container.setConcurrentConsumers(this.concurrentConsumers);
}
container.setConnectionFactory(this.connectionFactory);
if (this.errorHandler != null) {
container.setErrorHandler(this.errorHandler);
@@ -426,9 +449,6 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
if (this.prefetchCount != null) {
container.setPrefetchCount(this.prefetchCount);
}
if (this.receiveTimeout != null) {
container.setReceiveTimeout(this.receiveTimeout);
}
if (this.recoveryInterval != null) {
container.setRecoveryInterval(this.recoveryInterval);
}
@@ -444,9 +464,6 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
if (this.transactionManager != null) {
container.setTransactionManager(this.transactionManager);
}
if (this.txSize != null) {
container.setTxSize(this.txSize);
}
if (this.missingQueuesFatal != null) {
container.setMissingQueuesFatal(this.missingQueuesFatal);
}

View File

@@ -50,6 +50,21 @@ public class AmqpChannelParser extends AbstractChannelParser {
builder.addPropertyValue("pubSub", "publish-subscribe-channel".equals(element.getLocalName()));
String consumersPerQueue = element.getAttribute("consumers-per-queue");
if (StringUtils.hasText(consumersPerQueue)) {
if (StringUtils.hasText(element.getAttribute("concurrent-consumers"))) {
parserContext.getReaderContext().error("'consumers-per-queue' and 'concurrent-consumers' are mutually "
+ "exclusive", element);
}
if (StringUtils.hasText(element.getAttribute("tx-size"))) {
parserContext.getReaderContext().error("'tx-size' is not allowed with 'consumers-per-queue'", element);
}
if (StringUtils.hasText(element.getAttribute("receive-timeout"))) {
parserContext.getReaderContext().error("'receive-timeout' is not allowed with 'consumers-per-queue'",
element);
}
builder.addPropertyValue("consumersPerQueue", consumersPerQueue);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "max-subscribers");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "acknowledge-mode");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "advice-chain");

View File

@@ -874,7 +874,17 @@ standard headers to also be mapped. To map all non-standard headers the 'NON_STA
Specify the number of concurrent consumers to create. Default is 1.
Raising the number of concurrent consumers is recommended in order to scale the consumption of messages coming in
from a queue. However, note that any ordering guarantees are lost once multiple consumers are registered. In
general, stick with 1 consumer for low-volume queues.
general, use 1 consumer for low-volume queues. Mutually exclusive with 'consumers-per-queue'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="consumers-per-queue" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify the number of consumers to create for each queue.
Setting this attribute creates a 'DirectMessageListenerContainer' instead of the default
'SimpleMessageListenerContainer'. Refer to the Spring AMQP reference documentation for
more information about these containers.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -915,6 +925,7 @@ standard headers to also be mapped. To map all non-standard headers the 'NON_STA
<xsd:appinfo>
<xsd:documentation>
The timeout for each attempt by a consumer to receive the next message.
Not allowed when 'consumers-per-queue' is set.
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
@@ -980,6 +991,7 @@ standard headers to also be mapped. To map all non-standard headers the 'NON_STA
<xsd:documentation>
How many messages to process in a single transaction (if the channel is transactional). For best results it should be
less than or equal to the prefetch count.
Not allowed when 'consumers-per-queue' is set.
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
@@ -991,7 +1003,7 @@ standard headers to also be mapped. To map all non-standard headers the 'NON_STA
If 'true', and none of the queues are available on the broker, the container will throw a fatal exception during
startup and will stop if the queues are deleted when the container is running (after making 3 attempts to
passively declare the queues). If false, the container will not throw an exception and go into recovery mode,
attempting to restart according to the 'recovery-interval'. Default 'true'.
attempting to restart according to the 'recovery-interval'. Default 'true' unless 'consumers-per-queue' is set.
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>

View File

@@ -18,7 +18,7 @@
<bean id="rabbitConnectionFactory" class="org.springframework.integration.amqp.StubRabbitConnectionFactory"/>
<amqp:channel id="channelWithSubscriberLimit" max-subscribers="1" missing-queues-fatal="false"
template-channel-transacted="true"/>
template-channel-transacted="true" consumers-per-queue="2" />
<amqp:publish-subscribe-channel id="pubSub" />

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.amqp.config;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -30,6 +31,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.amqp.channel.AbstractAmqpChannel;
@@ -80,6 +83,7 @@ public class AmqpChannelParserTests {
assertTrue(TestUtils.getPropertyValue(channel, "container.missingQueuesFatal", Boolean.class));
assertFalse(TestUtils.getPropertyValue(channel, "container.transactional", Boolean.class));
assertFalse(TestUtils.getPropertyValue(channel, "amqpTemplate.transactional", Boolean.class));
assertThat(TestUtils.getPropertyValue(channel, "container"), instanceOf(SimpleMessageListenerContainer.class));
}
@Test
@@ -91,6 +95,8 @@ public class AmqpChannelParserTests {
assertFalse(TestUtils.getPropertyValue(channel, "container.transactional", Boolean.class));
assertTrue(TestUtils.getPropertyValue(channel, "amqpTemplate.transactional", Boolean.class));
assertFalse(TestUtils.getPropertyValue(channel, "extractPayload", Boolean.class));
assertThat(TestUtils.getPropertyValue(channel, "container"), instanceOf(DirectMessageListenerContainer.class));
assertEquals(2, TestUtils.getPropertyValue(channel, "container.consumersPerQueue"));
}
@Test

View File

@@ -43,6 +43,9 @@
channel="requestChannel" queue-names="inboundchanneladapter.test.2"
auto-startup="false" phase="123"/>
<amqp:inbound-channel-adapter id="dmlc" queue-names="inboundchanneladapter.test.2" consumers-per-queue="2"
auto-startup="false" />
<int:channel id="requestChannel">
<int:queue/>
</int:channel>

View File

@@ -16,10 +16,12 @@
package org.springframework.integration.amqp.config;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
@@ -30,6 +32,8 @@ import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
@@ -66,6 +70,18 @@ public class AmqpInboundChannelAdapterParserTests {
assertEquals(Boolean.TRUE, TestUtils.getPropertyValue(adapter, "autoStartup"));
assertEquals(Integer.MAX_VALUE / 2, TestUtils.getPropertyValue(adapter, "phase"));
assertTrue(TestUtils.getPropertyValue(adapter, "messageListenerContainer.missingQueuesFatal", Boolean.class));
assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer"),
instanceOf(SimpleMessageListenerContainer.class));
}
@Test
public void verifyDMCC() {
Object adapter = context.getBean("dmlc.adapter");
assertEquals(AmqpInboundChannelAdapter.class, adapter.getClass());
assertFalse(TestUtils.getPropertyValue(adapter, "messageListenerContainer.missingQueuesFatal", Boolean.class));
assertThat(TestUtils.getPropertyValue(adapter, "messageListenerContainer"),
instanceOf(DirectMessageListenerContainer.class));
assertEquals(2, TestUtils.getPropertyValue(adapter, "messageListenerContainer.consumersPerQueue"));
}
@Test