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:
Artem Bilan
2014-10-03 17:16:02 +03:00
committed by Gary Russell
parent eca2314ea9
commit cbd330f0e4
12 changed files with 252 additions and 113 deletions

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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>

View File

@@ -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());

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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" />

View File

@@ -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>&lt;rabbit:template&gt;</code> component.
</para>
</section>
<section id="cf-pub-conf-ret">
<title>Publisher Confirms and Returns</title>
<para>

View File

@@ -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>