Add back-off attribute to JMS namespace

This commit adds a "back-off" attribute to the jms:listener-container
 element so that a BackOff instance can be provided for users of the
 XML namespace.

 Issue: SPR-11746
This commit is contained in:
Stephane Nicoll 2014-05-09 15:05:43 +02:00
parent 6a0483128a
commit 49040a2925
6 changed files with 73 additions and 18 deletions

View File

@ -54,6 +54,8 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser {
private static final String RECOVERY_INTERVAL_ATTRIBUTE = "recovery-interval"; private static final String RECOVERY_INTERVAL_ATTRIBUTE = "recovery-interval";
private static final String BACK_OFF_ATTRIBUTE = "back-off";
protected PropertyValues parseProperties(Element containerEle, ParserContext parserContext) { protected PropertyValues parseProperties(Element containerEle, ParserContext parserContext) {
final MutablePropertyValues properties = new MutablePropertyValues(); final MutablePropertyValues properties = new MutablePropertyValues();
@ -223,10 +225,18 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser {
} }
} }
String recoveryInterval = containerEle.getAttribute(RECOVERY_INTERVAL_ATTRIBUTE); String backOffBeanName = containerEle.getAttribute(BACK_OFF_ATTRIBUTE);
if (StringUtils.hasText(recoveryInterval)) { if (StringUtils.hasText(backOffBeanName)) {
if (!isSimpleContainer) { if (!isSimpleContainer) {
propertyValues.add("recoveryInterval", recoveryInterval); propertyValues.add("backOff", new RuntimeBeanReference(backOffBeanName));
}
}
else { // No need to consider this if back-off is set
String recoveryInterval = containerEle.getAttribute(RECOVERY_INTERVAL_ATTRIBUTE);
if (StringUtils.hasText(recoveryInterval)) {
if (!isSimpleContainer) {
propertyValues.add("recoveryInterval", recoveryInterval);
}
} }
} }

View File

@ -224,6 +224,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
* between recovery attempts. If the {@link BackOff} implementation * between recovery attempts. If the {@link BackOff} implementation
* returns {@link BackOff#STOP}, this listener container will not further * returns {@link BackOff#STOP}, this listener container will not further
* attempt to recover. * attempt to recover.
* <p>The {@link #setRecoveryInterval(long) recovery interval} is ignored
* when this property is set.
*/ */
public void setBackOff(BackOff backOff) { public void setBackOff(BackOff backOff) {
this.backOff = backOff; this.backOff = backOff;
@ -231,9 +233,10 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
/** /**
* Specify the interval between recovery attempts, in <b>milliseconds</b>. * Specify the interval between recovery attempts, in <b>milliseconds</b>.
* The default is 5000 ms, that is, 5 seconds. * The default is 5000 ms, that is, 5 seconds. This is a convenience method
* <p>This is a convenience method to create a {@link FixedBackOff} with * to create a {@link FixedBackOff} with the specified interval.
* the specified interval. * <p>For more recovery options, consider specifying a {@link BackOff}
* instance instead.
* @see #setBackOff(BackOff) * @see #setBackOff(BackOff)
* @see #handleListenerSetupFailure * @see #handleListenerSetupFailure
*/ */

View File

@ -318,11 +318,29 @@
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="back-off" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify the BackOff instance to use to compute the interval between recovery
attempts. If the BackOff implementation returns "BackOff#STOP", the listener
container will not further attempt to recover. The recovery-interval value is
ignored when this property is set. The default is a FixedBackOff with an
interval of 5000 ms, that is 5 seconds.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.util.BackOff"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="recovery-interval" type="xsd:string"> <xsd:attribute name="recovery-interval" type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
Specify the interval between recovery attempts, in milliseconds. Specify the interval between recovery attempts, in milliseconds. Convenience
The default is 5000 ms, that is, 5 seconds. way to create a FixedBackOff with the specified interval. For more recovery
options, consider specifying a BackOff instance instead. The default is
5000 ms, that is 5 seconds.
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>

View File

@ -161,6 +161,7 @@ public class JmsNamespaceHandlerTests {
assertEquals("wrong concurrency", 3, container.getConcurrentConsumers()); assertEquals("wrong concurrency", 3, container.getConcurrentConsumers());
assertEquals("wrong concurrency", 5, container.getMaxConcurrentConsumers()); assertEquals("wrong concurrency", 5, container.getMaxConcurrentConsumers());
assertEquals("wrong prefetch", 50, container.getMaxMessagesPerTask()); assertEquals("wrong prefetch", 50, container.getMaxMessagesPerTask());
assertSame(context.getBean("testBackOff"),new DirectFieldAccessor(container).getPropertyValue("backOff"));
assertEquals("phase cannot be customized by the factory", Integer.MAX_VALUE, container.getPhase()); assertEquals("phase cannot be customized by the factory", Integer.MAX_VALUE, container.getPhase());
} }
@ -216,12 +217,13 @@ public class JmsNamespaceHandlerTests {
@Test @Test
public void testRecoveryInterval() { public void testRecoveryInterval() {
long recoveryInterval1 = getRecoveryInterval("listener1"); Object testBackOff = context.getBean("testBackOff");
long recoveryInterval2 = getRecoveryInterval("listener2"); BackOff backOff1 = getBackOff("listener1");
BackOff backOff2 = getBackOff("listener2");
long recoveryInterval3 = getRecoveryInterval(DefaultMessageListenerContainer.class.getName() + "#0"); long recoveryInterval3 = getRecoveryInterval(DefaultMessageListenerContainer.class.getName() + "#0");
assertEquals(1000L, recoveryInterval1); assertSame(testBackOff, backOff1);
assertEquals(1000L, recoveryInterval2); assertSame(testBackOff, backOff2);
assertEquals(DefaultMessageListenerContainer.DEFAULT_RECOVERY_INTERVAL, recoveryInterval3); assertEquals(DefaultMessageListenerContainer.DEFAULT_RECOVERY_INTERVAL, recoveryInterval3);
} }
@ -300,9 +302,13 @@ public class JmsNamespaceHandlerTests {
return (ErrorHandler) new DirectFieldAccessor(container).getPropertyValue("errorHandler"); return (ErrorHandler) new DirectFieldAccessor(container).getPropertyValue("errorHandler");
} }
private long getRecoveryInterval(String containerBeanName) { private BackOff getBackOff(String containerBeanName) {
DefaultMessageListenerContainer container = this.context.getBean(containerBeanName, DefaultMessageListenerContainer.class); DefaultMessageListenerContainer container = this.context.getBean(containerBeanName, DefaultMessageListenerContainer.class);
BackOff backOff = (BackOff) new DirectFieldAccessor(container).getPropertyValue("backOff"); return (BackOff) new DirectFieldAccessor(container).getPropertyValue("backOff");
}
private long getRecoveryInterval(String containerBeanName) {
BackOff backOff = getBackOff(containerBeanName);
assertEquals(FixedBackOff.class, backOff.getClass()); assertEquals(FixedBackOff.class, backOff.getClass());
return ((FixedBackOff)backOff).getInterval(); return ((FixedBackOff)backOff).getInterval();
} }

View File

@ -9,7 +9,7 @@
connection-factory="testConnectionFactory" task-executor="testTaskExecutor" connection-factory="testConnectionFactory" task-executor="testTaskExecutor"
destination-resolver="testDestinationResolver" message-converter="testMessageConverter" destination-resolver="testDestinationResolver" message-converter="testMessageConverter"
transaction-manager="testTransactionManager" error-handler="testErrorHandler" transaction-manager="testTransactionManager" error-handler="testErrorHandler"
cache="connection" concurrency="3-5" prefetch="50" receive-timeout="100" recovery-interval="1000" phase="99"> cache="connection" concurrency="3-5" prefetch="50" receive-timeout="100" back-off="testBackOff" phase="99">
<jms:listener id="listener1" destination="testDestination" ref="testBean1" method="setName"/> <jms:listener id="listener1" destination="testDestination" ref="testBean1" method="setName"/>
<jms:listener id="listener2" destination="testDestination" ref="testBean2" method="setName" response-destination="responseDestination"/> <jms:listener id="listener2" destination="testDestination" ref="testBean2" method="setName" response-destination="responseDestination"/>
</jms:listener-container> </jms:listener-container>
@ -43,7 +43,8 @@
connection-factory="testConnectionFactory" task-executor="testTaskExecutor" connection-factory="testConnectionFactory" task-executor="testTaskExecutor"
destination-resolver="testDestinationResolver" message-converter="testMessageConverter" destination-resolver="testDestinationResolver" message-converter="testMessageConverter"
transaction-manager="testTransactionManager" error-handler="testErrorHandler" transaction-manager="testTransactionManager" error-handler="testErrorHandler"
concurrency="3-5" prefetch="50" receive-timeout="100" recovery-interval="1000"/> concurrency="3-5" prefetch="50" receive-timeout="100"
recovery-interval="1000" back-off="testBackOff"/>
<!-- the default ConnectionFactory --> <!-- the default ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.StubConnectionFactory"/> <bean id="connectionFactory" class="org.springframework.jms.StubConnectionFactory"/>
@ -67,6 +68,10 @@
<bean id="testErrorHandler" class="org.springframework.jms.config.JmsNamespaceHandlerTests$TestErrorHandler"/> <bean id="testErrorHandler" class="org.springframework.jms.config.JmsNamespaceHandlerTests$TestErrorHandler"/>
<bean id="testBackOff" class="org.springframework.util.FixedBackOff">
<property name="interval" value="1000"/>
</bean>
<bean id="testBean1" class="org.springframework.tests.sample.beans.TestBean"/> <bean id="testBean1" class="org.springframework.tests.sample.beans.TestBean"/>
<bean id="testBean2" class="org.springframework.tests.sample.beans.TestBean"/> <bean id="testBean2" class="org.springframework.tests.sample.beans.TestBean"/>

View File

@ -40424,6 +40424,10 @@ This listener container strikes a good balance between low requirements on the J
provider, advanced functionality such as transaction participation, and compatibility provider, advanced functionality such as transaction participation, and compatibility
with Java EE environments. with Java EE environments.
This container also has recoverable capabilities when the broker goes down. By default,
a simple `BackOff` implementation retries every 5 seconds. It is possible to specify
a custom `BackOff` implementation for more fine-grained recovery options, see
`ExponentialBackOff` for an example.
[[jms-tx]] [[jms-tx]]
@ -41427,9 +41431,18 @@ choices and message redelivery scenarios.
| The timeout to use for receive calls (in milliseconds). The default is `1000` ms (1 | The timeout to use for receive calls (in milliseconds). The default is `1000` ms (1
sec); `-1` indicates no timeout at all. sec); `-1` indicates no timeout at all.
| back-off
| Specify the `BackOff` instance to use to compute the interval between recovery
attempts. If the `BackOff` implementation returns `BackOff#STOP`, the listener
container will not further attempt to recover. The `recovery-interval value is
is ignored when this property is set. The default is a `FixedBackOff` with an
interval of 5000 ms, that is 5 seconds.
| recovery-interval | recovery-interval
| Specify the interval between recovery attempts, in milliseconds. The default is `5000` | Specify the interval between recovery attempts, in milliseconds. Convenience
ms, that is, 5 seconds. way to create a `FixedBackOff` with the specified interval. For more recovery
options, consider specifying a BackOff instance instead. The default is 5000 ms,
that is 5 seconds.
| phase | phase
| The lifecycle phase within which this container should start and stop. The lower the | The lifecycle phase within which this container should start and stop. The lower the