diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/TemplateParser.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/TemplateParser.java index b07449a6..ec613f3f 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/TemplateParser.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/TemplateParser.java @@ -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 childElements = DomUtils.getChildElementsByTagName(element, LISTENER_ELEMENT); diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.java index ccb5173e..97b24b63 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/AbstractRoutingConnectionFactory.java @@ -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); } diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitAccessor.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitAccessor.java index bbb5a6ed..82fb764f 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitAccessor.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitAccessor.java @@ -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) { diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java index 1ba602b1..b24b2f37 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java @@ -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() { @@ -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}. + *

+ * The result of this expression is used as {@code lookupKey} to get the target + * {@link ConnectionFactory} from {@link AbstractRoutingConnectionFactory} + * directly. + *

+ * If this expression is evaluated to {@code null}, we fallback to the normal + * {@link AbstractRoutingConnectionFactory} logic. + *

+ * 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}. + *

+ * 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}. + *

+ * The result of this expression is used as {@code lookupKey} to get the target + * {@link ConnectionFactory} from {@link AbstractRoutingConnectionFactory} + * directly. + *

+ * If this expression is evaluated to {@code null}, we fallback to the normal + * {@link AbstractRoutingConnectionFactory} logic. + *

+ * 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}. + *

+ * 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 execute(final ChannelCallback action) { + public T execute(ChannelCallback action) { + return execute(action, null); + } + + private T execute(final ChannelCallback action, final ConnectionFactory connectionFactory) { if (this.retryTemplate != null) { try { return this.retryTemplate.execute(new RetryCallback() { @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 doExecute(ChannelCallback action) { + + private T doExecute(ChannelCallback 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); diff --git a/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-1.4.xsd b/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-1.4.xsd index 8e7912f9..69abae80 100644 --- a/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-1.4.xsd +++ b/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-1.4.xsd @@ -1048,6 +1048,27 @@ + + + + + + + + + + diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/TemplateParserTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/TemplateParserTests.java index e8e77ed3..9ecaf8d7 100644 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/TemplateParserTests.java +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/TemplateParserTests.java @@ -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()); diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java index d311610e..d3f2f057 100644 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java @@ -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 diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java index f1697eed..c2e3f593 100644 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java @@ -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 factories = + new HashMap(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(); + } + } diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Tester.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Tester.java deleted file mode 100644 index 7905dc3a..00000000 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Tester.java +++ /dev/null @@ -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 unconfirmedSet = Collections.synchronizedSortedSet(new TreeSet()); - - 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(); - } - -} diff --git a/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/TemplateParserTests-context.xml b/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/TemplateParserTests-context.xml index c94d207e..5e02af86 100644 --- a/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/TemplateParserTests-context.xml +++ b/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/TemplateParserTests-context.xml @@ -28,7 +28,9 @@ mandatory="true" return-callback="rcb" confirm-callback="ccb" /> + mandatory-expression="'true'" + send-connection-factory-selector-expression="'foo'" + receive-connection-factory-selector-expression="'foo'"/> diff --git a/src/reference/docbook/amqp.xml b/src/reference/docbook/amqp.xml index 58fa75ec..16fb062b 100644 --- a/src/reference/docbook/amqp.xml +++ b/src/reference/docbook/amqp.xml @@ -439,7 +439,38 @@ trustStore.passPhrase=secret It is important to unbind the resource after use. For more information see the JavaDocs of AbstractRoutingConnectionFactory. + + Starting with version 1.4, the RabbitTemplate supports + the SpEL sendConnectionFactorySelectorExpression and + receiveConnectionFactorySelectorExpression properties, which are evaluated on each AMQP + protocol interaction operation (send, sendAndReceive, receive or + receiveAndReply), resolving to a lookupKey value for the provided + AbstractRoutingConnectionFactory. Bean references, such as + "@vHostResolver.getVHost(#root)" can be used in the expression. For send + operations, the Message to be sent is the root evaluation object; for receive operations, the + queueName is the root evaluation object. + + + The routing algorithm is: If the selector expression + is null, or is evaluated to null, or the provided + ConnectionFactory isn't an instance of + AbstractRoutingConnectionFactory, everything works as before, relying on the provided + ConnectionFactory implementation. The same occurs if the + evaluation result isn't null, but there is no target + ConnectionFactory for that lookupKey and the + AbstractRoutingConnectionFactory is configured with + lenientFallback = true. Of course, in the case of an + AbstractRoutingConnectionFactory it does fallback to its routing + implementation based on determineCurrentLookupKey(). But, if + lenientFallback = false, an IllegalStateException is thrown. + + + The Namespace support also provides the send-connection-factory-selector-expression + and receive-connection-factory-selector-expression attributes + on the <rabbit:template> component. + +

Publisher Confirms and Returns diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index de2b0a52..c3c6776a 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -89,12 +89,21 @@
- RabbitTemplate: mandatoryExpression + RabbitTemplate: mandatory and connectionFactorySelector Expressions - The mandatoryExpression SpEL Expression property - has been added to the RabbitTemplate to evaluate a mandatory + The mandatoryExpression and sendConnectionFactorySelectorExpression + and receiveConnectionFactorySelectorExpression SpEL + Expressions properties + have been added to the RabbitTemplate. + The mandatoryExpression is used to evaluate a mandatory boolean value against each request message, when a ReturnCallback is in use. See . + The sendConnectionFactorySelectorExpression and + receiveConnectionFactorySelectorExpression are used when an + AbstractRoutingConnectionFactory is provided, to determine the + lookupKey for the target ConnectionFactory at runtime on + each AMQP protocol interaction operation. + See .