AMQP-406: Add RoutingCF to the RabbitTemplate
JIRA: https://jira.spring.io/browse/AMQP-406 * Add support of `connection-factory-selector-expression` for the `RabbitTemplate` and provide logic to bind target `ConnectionFactory` to the invocation context based on the result of the expression evaluation. AMQP-406: Fix double ThreadLocal resource Polishing AMQP-406: introduce `receiveConnectionFactorySelectorExpression` Doc Polishing
This commit is contained in:
committed by
Gary Russell
parent
eca2314ea9
commit
cbd330f0e4
@@ -112,6 +112,21 @@ class TemplateParser extends AbstractSingleBeanDefinitionParser {
|
||||
builder.addPropertyValue("mandatoryExpression", expressionDef);
|
||||
}
|
||||
|
||||
BeanDefinition sendConnectionFactorySelectorExpression =
|
||||
NamespaceUtils.createExpressionDefIfAttributeDefined("send-connection-factory-selector-expression",
|
||||
element);
|
||||
if (sendConnectionFactorySelectorExpression != null) {
|
||||
builder.addPropertyValue("sendConnectionFactorySelectorExpression", sendConnectionFactorySelectorExpression);
|
||||
}
|
||||
|
||||
BeanDefinition receiveConnectionFactorySelectorExpression =
|
||||
NamespaceUtils.createExpressionDefIfAttributeDefined("receive-connection-factory-selector-expression",
|
||||
element);
|
||||
if (receiveConnectionFactorySelectorExpression != null) {
|
||||
builder.addPropertyValue("receiveConnectionFactorySelectorExpression",
|
||||
receiveConnectionFactorySelectorExpression);
|
||||
}
|
||||
|
||||
BeanDefinition replyContainer = null;
|
||||
Element childElement = null;
|
||||
List<Element> childElements = DomUtils.getChildElementsByTagName(element, LISTENER_ELEMENT);
|
||||
|
||||
@@ -84,6 +84,10 @@ public abstract class AbstractRoutingConnectionFactory implements ConnectionFact
|
||||
this.lenientFallback = lenientFallback;
|
||||
}
|
||||
|
||||
public boolean isLenientFallback() {
|
||||
return lenientFallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection createConnection() throws AmqpException {
|
||||
return this.determineTargetConnectionFactory().createConnection();
|
||||
@@ -178,7 +182,7 @@ public abstract class AbstractRoutingConnectionFactory implements ConnectionFact
|
||||
* @param key The lookup key of which the {@link ConnectionFactory} is bound
|
||||
* @return the {@link ConnectionFactory} bound to given lookup key, null if one does not exist
|
||||
*/
|
||||
protected ConnectionFactory getTargetConnectionFactory(Object key) {
|
||||
public ConnectionFactory getTargetConnectionFactory(Object key) {
|
||||
return targetConnectionFactories.get(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,9 +103,7 @@ public abstract class RabbitAccessor implements InitializingBean {
|
||||
}
|
||||
|
||||
protected RabbitResourceHolder getTransactionalResourceHolder() {
|
||||
RabbitResourceHolder holder = ConnectionFactoryUtils.getTransactionalResourceHolder(this.connectionFactory,
|
||||
isChannelTransacted());
|
||||
return holder;
|
||||
return ConnectionFactoryUtils.getTransactionalResourceHolder(this.connectionFactory, isChannelTransacted());
|
||||
}
|
||||
|
||||
protected RuntimeException convertRabbitAccessException(Exception ex) {
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.ReceiveAndReplyCallback;
|
||||
import org.springframework.amqp.core.ReceiveAndReplyMessageCallback;
|
||||
import org.springframework.amqp.core.ReplyToAddressCallback;
|
||||
import org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ChannelProxy;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils;
|
||||
@@ -158,6 +159,10 @@ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware,
|
||||
|
||||
private volatile RetryTemplate retryTemplate;
|
||||
|
||||
private volatile Expression sendConnectionFactorySelectorExpression;
|
||||
|
||||
private volatile Expression receiveConnectionFactorySelectorExpression;
|
||||
|
||||
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
|
||||
private final ReplyToAddressCallback<?> defaultReplyToAddressCallback = new ReplyToAddressCallback<Object>() {
|
||||
@@ -326,6 +331,54 @@ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware,
|
||||
this.mandatoryExpression = mandatoryExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* A SpEL {@link Expression} to evaluate
|
||||
* against each request message, if the provided {@link #getConnectionFactory()}
|
||||
* is an instance of {@link AbstractRoutingConnectionFactory}.
|
||||
* <p>
|
||||
* The result of this expression is used as {@code lookupKey} to get the target
|
||||
* {@link ConnectionFactory} from {@link AbstractRoutingConnectionFactory}
|
||||
* directly.
|
||||
* <p>
|
||||
* If this expression is evaluated to {@code null}, we fallback to the normal
|
||||
* {@link AbstractRoutingConnectionFactory} logic.
|
||||
* <p>
|
||||
* If there is no target {@link ConnectionFactory} with the evaluated {@code lookupKey},
|
||||
* we fallback to the normal {@link AbstractRoutingConnectionFactory} logic
|
||||
* only if its property {@code lenientFallback == true}.
|
||||
* <p>
|
||||
* This expression is used for {@code send} operations.
|
||||
* @param sendConnectionFactorySelectorExpression a SpEL {@link Expression} to evaluate
|
||||
* @since 1.4
|
||||
*/
|
||||
public void setSendConnectionFactorySelectorExpression(Expression sendConnectionFactorySelectorExpression) {
|
||||
this.sendConnectionFactorySelectorExpression = sendConnectionFactorySelectorExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* A SpEL {@link Expression} to evaluate
|
||||
* against each {@code receive} {@code queueName}, if the provided {@link #getConnectionFactory()}
|
||||
* is an instance of {@link AbstractRoutingConnectionFactory}.
|
||||
* <p>
|
||||
* The result of this expression is used as {@code lookupKey} to get the target
|
||||
* {@link ConnectionFactory} from {@link AbstractRoutingConnectionFactory}
|
||||
* directly.
|
||||
* <p>
|
||||
* If this expression is evaluated to {@code null}, we fallback to the normal
|
||||
* {@link AbstractRoutingConnectionFactory} logic.
|
||||
* <p>
|
||||
* If there is no target {@link ConnectionFactory} with the evaluated {@code lookupKey},
|
||||
* we fallback to the normal {@link AbstractRoutingConnectionFactory} logic
|
||||
* only if its property {@code lenientFallback == true}.
|
||||
* <p>
|
||||
* This expression is used for {@code receive} operations.
|
||||
* @param receiveConnectionFactorySelectorExpression a SpEL {@link Expression} to evaluate
|
||||
* @since 1.4
|
||||
*/
|
||||
public void setReceiveConnectionFactorySelectorExpression(Expression receiveConnectionFactorySelectorExpression) {
|
||||
this.receiveConnectionFactorySelectorExpression = receiveConnectionFactorySelectorExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to 'correlationId' (default) the correlationId property
|
||||
* will be used; otherwise the supplied key will be used.
|
||||
@@ -406,7 +459,32 @@ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware,
|
||||
doSend(channel, exchange, routingKey, message, correlationData);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, obtainTargetConnectionFactoryIfNecessary(this.sendConnectionFactorySelectorExpression, message));
|
||||
}
|
||||
|
||||
private ConnectionFactory obtainTargetConnectionFactoryIfNecessary(Expression expression, Object rootObject) {
|
||||
if (expression != null && getConnectionFactory() instanceof AbstractRoutingConnectionFactory) {
|
||||
AbstractRoutingConnectionFactory routingConnectionFactory =
|
||||
(AbstractRoutingConnectionFactory) getConnectionFactory();
|
||||
Object lookupKey = null;
|
||||
if (rootObject != null) {
|
||||
lookupKey = this.sendConnectionFactorySelectorExpression.getValue(this.evaluationContext, rootObject);
|
||||
}
|
||||
else {
|
||||
lookupKey = this.sendConnectionFactorySelectorExpression.getValue(this.evaluationContext);
|
||||
}
|
||||
if (lookupKey != null) {
|
||||
ConnectionFactory connectionFactory = routingConnectionFactory.getTargetConnectionFactory(lookupKey);
|
||||
if (connectionFactory != null) {
|
||||
return connectionFactory;
|
||||
}
|
||||
else if (!routingConnectionFactory.isLenientFallback()) {
|
||||
throw new IllegalStateException("Cannot determine target ConnectionFactory for lookup key ["
|
||||
+ lookupKey + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -502,7 +580,7 @@ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware,
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, obtainTargetConnectionFactoryIfNecessary(this.receiveConnectionFactorySelectorExpression, queueName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -644,7 +722,7 @@ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware,
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}, obtainTargetConnectionFactoryIfNecessary(this.receiveConnectionFactorySelectorExpression, queueName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -768,7 +846,7 @@ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware,
|
||||
channel.basicCancel(consumerTag);
|
||||
return reply;
|
||||
}
|
||||
});
|
||||
}, obtainTargetConnectionFactoryIfNecessary(this.sendConnectionFactorySelectorExpression, message));
|
||||
}
|
||||
|
||||
protected Message doSendAndReceiveWithFixed(final String exchange, final String routingKey, final Message message) {
|
||||
@@ -820,18 +898,22 @@ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware,
|
||||
RabbitTemplate.this.replyHolder.remove(messageTag);
|
||||
return reply;
|
||||
}
|
||||
});
|
||||
}, obtainTargetConnectionFactoryIfNecessary(this.sendConnectionFactorySelectorExpression, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T execute(final ChannelCallback<T> action) {
|
||||
public <T> T execute(ChannelCallback<T> action) {
|
||||
return execute(action, null);
|
||||
}
|
||||
|
||||
private <T> T execute(final ChannelCallback<T> action, final ConnectionFactory connectionFactory) {
|
||||
if (this.retryTemplate != null) {
|
||||
try {
|
||||
return this.retryTemplate.execute(new RetryCallback<T, Exception>() {
|
||||
|
||||
@Override
|
||||
public T doWithRetry(RetryContext context) throws Exception {
|
||||
return RabbitTemplate.this.doExecute(action);
|
||||
return RabbitTemplate.this.doExecute(action, connectionFactory);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -844,13 +926,15 @@ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware,
|
||||
}
|
||||
}
|
||||
else {
|
||||
return this.doExecute(action);
|
||||
return this.doExecute(action, connectionFactory);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T doExecute(ChannelCallback<T> action) {
|
||||
|
||||
private <T> T doExecute(ChannelCallback<T> action, ConnectionFactory connectionFactory) {
|
||||
Assert.notNull(action, "Callback object must not be null");
|
||||
RabbitResourceHolder resourceHolder = getTransactionalResourceHolder();
|
||||
RabbitResourceHolder resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(
|
||||
(connectionFactory != null ? connectionFactory : getConnectionFactory()), isChannelTransacted());
|
||||
Channel channel = resourceHolder.getChannel();
|
||||
if (this.confirmCallback != null || this.returnCallback != null) {
|
||||
addListener(channel);
|
||||
|
||||
@@ -1048,6 +1048,27 @@
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="send-connection-factory-selector-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A SpEL expression to evaluate the 'lookupKey' for each request message,
|
||||
when an 'AbstractRoutingConnectionFactory' is in used for this 'template'.
|
||||
The 'BeanFactoryResolver' is available too, if the RabbitTemplate is used from Spring Context,
|
||||
allowing for expressions such as '@vhostFor.select(messageProperties.headers.customerId)'.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="receive-connection-factory-selector-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A SpEL expression to evaluate the 'lookupKey' for each 'receive' operation,
|
||||
when an 'AbstractRoutingConnectionFactory' is in used for this 'template'; the root object
|
||||
for the evaluation is the queue name.
|
||||
The 'BeanFactoryResolver' is available too, if the RabbitTemplate is used from Spring Context,
|
||||
allowing for expressions such as '@vhostFor.select(#root)'.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
@@ -76,6 +76,10 @@ public final class TemplateParserTests {
|
||||
AmqpTemplate template = beanFactory.getBean("withMandatoryExpression", AmqpTemplate.class);
|
||||
assertNotNull(template);
|
||||
assertEquals("'true'", TestUtils.getPropertyValue(template, "mandatoryExpression.expression"));
|
||||
assertEquals("'foo'",
|
||||
TestUtils.getPropertyValue(template, "sendConnectionFactorySelectorExpression.expression"));
|
||||
assertEquals("'foo'",
|
||||
TestUtils.getPropertyValue(template, "receiveConnectionFactorySelectorExpression.expression"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,11 +102,13 @@ public final class TemplateParserTests {
|
||||
assertNotNull(queue);
|
||||
Queue queueBean = beanFactory.getBean("reply.queue", Queue.class);
|
||||
assertSame(queueBean, queue);
|
||||
SimpleMessageListenerContainer container = beanFactory.getBean("withReplyQ.replyListener", SimpleMessageListenerContainer.class);
|
||||
SimpleMessageListenerContainer container =
|
||||
beanFactory.getBean("withReplyQ.replyListener", SimpleMessageListenerContainer.class);
|
||||
assertNotNull(container);
|
||||
dfa = new DirectFieldAccessor(container);
|
||||
assertSame(template, dfa.getPropertyValue("messageListener"));
|
||||
SimpleMessageListenerContainer messageListenerContainer = beanFactory.getBean(SimpleMessageListenerContainer.class);
|
||||
SimpleMessageListenerContainer messageListenerContainer =
|
||||
beanFactory.getBean(SimpleMessageListenerContainer.class);
|
||||
dfa = new DirectFieldAccessor(messageListenerContainer);
|
||||
Collection<?> queueNames = (Collection<?>) dfa.getPropertyValue("queueNames");
|
||||
assertEquals(1, queueNames.size());
|
||||
|
||||
@@ -64,6 +64,7 @@ import org.springframework.amqp.rabbit.test.BrokerTestUtils;
|
||||
import org.springframework.amqp.support.converter.SimpleMessageConverter;
|
||||
import org.springframework.amqp.utils.SerializationUtils;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
@@ -102,6 +103,7 @@ public class RabbitTemplateIntegrationTests {
|
||||
connectionFactory.setHost("localhost");
|
||||
connectionFactory.setPort(BrokerTestUtils.getPort());
|
||||
template = new RabbitTemplate(connectionFactory);
|
||||
template.setSendConnectionFactorySelectorExpression(new LiteralExpression("foo"));
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.amqp.rabbit.core;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
@@ -24,6 +25,8 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -36,10 +39,15 @@ import org.mockito.stubbing.Answer;
|
||||
import org.springframework.amqp.AmqpAuthenticationException;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.core.ReceiveAndReplyCallback;
|
||||
import org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.SingleConnectionFactory;
|
||||
import org.springframework.amqp.support.converter.SimpleMessageConverter;
|
||||
import org.springframework.amqp.utils.SerializationUtils;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
@@ -60,6 +68,7 @@ import com.rabbitmq.client.impl.AMQImpl;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @since 1.0.1
|
||||
*
|
||||
*/
|
||||
@@ -120,7 +129,7 @@ public class RabbitTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertbytes() {
|
||||
public void testConvertBytes() {
|
||||
RabbitTemplate template = new RabbitTemplate();
|
||||
byte[] payload = "Hello, world!".getBytes();
|
||||
Message message = template.convertMessageIfNecessary(payload);
|
||||
@@ -206,4 +215,54 @@ public class RabbitTemplateTests {
|
||||
assertEquals(3, count.get());
|
||||
}
|
||||
|
||||
public final static AtomicInteger LOOKUP_KEY_COUNT = new AtomicInteger();
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testRoutingConnectionFactory() throws Exception {
|
||||
org.springframework.amqp.rabbit.connection.ConnectionFactory connectionFactory1 =
|
||||
Mockito.mock(org.springframework.amqp.rabbit.connection.ConnectionFactory.class);
|
||||
org.springframework.amqp.rabbit.connection.ConnectionFactory connectionFactory2 =
|
||||
Mockito.mock(org.springframework.amqp.rabbit.connection.ConnectionFactory.class);
|
||||
Map<Object, org.springframework.amqp.rabbit.connection.ConnectionFactory> factories =
|
||||
new HashMap<Object, org.springframework.amqp.rabbit.connection.ConnectionFactory>(2);
|
||||
factories.put("foo", connectionFactory1);
|
||||
factories.put("bar", connectionFactory2);
|
||||
|
||||
|
||||
AbstractRoutingConnectionFactory connectionFactory = new SimpleRoutingConnectionFactory();
|
||||
connectionFactory.setTargetConnectionFactories(factories);
|
||||
|
||||
final RabbitTemplate template = new RabbitTemplate(connectionFactory);
|
||||
Expression expression = new SpelExpressionParser()
|
||||
.parseExpression("T(org.springframework.amqp.rabbit.core.RabbitTemplateTests)" +
|
||||
".LOOKUP_KEY_COUNT.getAndIncrement() % 2 == 0 ? 'foo' : 'bar'");
|
||||
template.setSendConnectionFactorySelectorExpression(expression);
|
||||
template.setReceiveConnectionFactorySelectorExpression(expression);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
try {
|
||||
template.convertAndSend("foo", "bar", "baz");
|
||||
}
|
||||
catch (Exception e) {
|
||||
//Ignore it. Doesn't matter for this test.
|
||||
}
|
||||
try {
|
||||
template.receive("foo");
|
||||
}
|
||||
catch (Exception e) {
|
||||
//Ignore it. Doesn't matter for this test.
|
||||
}
|
||||
try {
|
||||
template.receiveAndReply("foo", mock(ReceiveAndReplyCallback.class));
|
||||
}
|
||||
catch (Exception e) {
|
||||
//Ignore it. Doesn't matter for this test.
|
||||
}
|
||||
}
|
||||
|
||||
Mockito.verify(connectionFactory1, Mockito.times(5)).createConnection();
|
||||
Mockito.verify(connectionFactory2, Mockito.times(4)).createConnection();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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
|
||||
*
|
||||
* http://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.amqp.rabbit.core;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.amqp.AmqpIOException;
|
||||
|
||||
import com.rabbitmq.client.AMQP;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.ConfirmListener;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
public class Tester {
|
||||
|
||||
@Test
|
||||
public void testDealLockOnConfirmChannelClose() throws Exception {
|
||||
ConnectionFactory factory = new ConnectionFactory();
|
||||
Connection conn = factory.newConnection();
|
||||
final Channel channel = conn.createChannel();
|
||||
|
||||
final SortedSet<Long> unconfirmedSet = Collections.<Long>synchronizedSortedSet(new TreeSet<Long>());
|
||||
|
||||
channel.addConfirmListener(new ConfirmListener() {
|
||||
public void handleAck(long seqNo, boolean multiple) {
|
||||
System.out.println(seqNo + " " + multiple);
|
||||
if (multiple) {
|
||||
unconfirmedSet.headSet(seqNo + 1).clear();
|
||||
}
|
||||
else {
|
||||
unconfirmedSet.remove(seqNo);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleNack(long seqNo, boolean multiple) {
|
||||
// handle the lost messages somehow
|
||||
}
|
||||
});
|
||||
|
||||
channel.confirmSelect();
|
||||
for (long i = 0; i < 10; ++i) {
|
||||
unconfirmedSet.add(channel.getNextPublishSeqNo());
|
||||
channel.basicPublish("", "test.queue", new AMQP.BasicProperties.Builder().build(), "nop".getBytes());
|
||||
}
|
||||
|
||||
while (unconfirmedSet.size() > 0)
|
||||
Thread.sleep(100);
|
||||
|
||||
Executors.newSingleThreadExecutor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AmqpIOException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(unconfirmedSet.isEmpty());
|
||||
conn.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,9 @@
|
||||
mandatory="true" return-callback="rcb" confirm-callback="ccb" />
|
||||
|
||||
<rabbit:template id="withMandatoryExpression" connection-factory="connectionFactory"
|
||||
mandatory-expression="'true'"/>
|
||||
mandatory-expression="'true'"
|
||||
send-connection-factory-selector-expression="'foo'"
|
||||
receive-connection-factory-selector-expression="'foo'"/>
|
||||
|
||||
<beans:bean id="rcb" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.amqp.rabbit.core.RabbitTemplate$ReturnCallback" />
|
||||
|
||||
@@ -439,7 +439,38 @@ trustStore.passPhrase=secret</programlisting>
|
||||
It is important to unbind the resource after use.
|
||||
For more information see the JavaDocs of <classname>AbstractRoutingConnectionFactory</classname>.
|
||||
</para>
|
||||
<para>
|
||||
Starting with <emphasis>version 1.4</emphasis>, the <classname>RabbitTemplate</classname> supports
|
||||
the SpEL <code>sendConnectionFactorySelectorExpression</code> and
|
||||
<code>receiveConnectionFactorySelectorExpression</code> properties, which are evaluated on each AMQP
|
||||
protocol interaction operation (<code>send</code>, <code>sendAndReceive</code>, <code>receive</code> or
|
||||
<code>receiveAndReply</code>), resolving to a <code>lookupKey</code> value for the provided
|
||||
<classname>AbstractRoutingConnectionFactory</classname>. Bean references, such as
|
||||
<code>"@vHostResolver.getVHost(#root)"</code> can be used in the expression. For <code>send</code>
|
||||
operations, the Message to be sent is the root evaluation object; for <code>receive</code> operations, the
|
||||
<emphasis>queueName</emphasis> is the root evaluation object.
|
||||
</para>
|
||||
<para>
|
||||
The <emphasis>routing</emphasis> algorithm is: If the selector expression
|
||||
is <code>null</code>, or is evaluated to <code>null</code>, or the provided
|
||||
<interfacename>ConnectionFactory</interfacename> isn't an instance of
|
||||
<classname>AbstractRoutingConnectionFactory</classname>, everything works as before, relying on the provided
|
||||
<interfacename>ConnectionFactory</interfacename> implementation. The same occurs if the
|
||||
evaluation result isn't <code>null</code>, but there is no target
|
||||
<interfacename>ConnectionFactory</interfacename> for that <code>lookupKey</code> and the
|
||||
<classname>AbstractRoutingConnectionFactory</classname> is configured with
|
||||
<code>lenientFallback = true</code>. Of course, in the case of an
|
||||
<classname>AbstractRoutingConnectionFactory</classname> it does fallback to its <code>routing</code>
|
||||
implementation based on <code>determineCurrentLookupKey()</code>. But, if
|
||||
<code>lenientFallback = false</code>, an <classname>IllegalStateException</classname> is thrown.
|
||||
</para>
|
||||
<para>
|
||||
The Namespace support also provides the <code>send-connection-factory-selector-expression</code>
|
||||
and <code>receive-connection-factory-selector-expression</code> attributes
|
||||
on the <code><rabbit:template></code> component.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="cf-pub-conf-ret">
|
||||
<title>Publisher Confirms and Returns</title>
|
||||
<para>
|
||||
|
||||
@@ -89,12 +89,21 @@
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>RabbitTemplate: mandatoryExpression</title>
|
||||
<title>RabbitTemplate: mandatory and connectionFactorySelector Expressions</title>
|
||||
<para>
|
||||
The <code>mandatoryExpression</code> SpEL <interfacename>Expression</interfacename> property
|
||||
has been added to the <classname>RabbitTemplate</classname> to evaluate a <code>mandatory</code>
|
||||
The <code>mandatoryExpression</code> and <code>sendConnectionFactorySelectorExpression</code>
|
||||
and <code>receiveConnectionFactorySelectorExpression</code> SpEL
|
||||
<interfacename>Expression</interfacename>s properties
|
||||
have been added to the <classname>RabbitTemplate</classname>.
|
||||
The <code>mandatoryExpression</code> is used to evaluate a <code>mandatory</code>
|
||||
boolean value against each request message, when a <classname>ReturnCallback</classname> is in use.
|
||||
See <xref linkend="template-confirms"/>.
|
||||
The <code>sendConnectionFactorySelectorExpression</code> and
|
||||
<code>receiveConnectionFactorySelectorExpression</code> are used when an
|
||||
<classname>AbstractRoutingConnectionFactory</classname> is provided, to determine the
|
||||
<code>lookupKey</code> for the target <interfacename>ConnectionFactory</interfacename> at runtime on
|
||||
each AMQP protocol interaction operation.
|
||||
See <xref linkend="routing-connection-factory"/>.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user