Customize concurrency at listener level

Prior to this commit, customizing the concurrency to use fo a given JMS
listener involved to define it in a specific listener-container. As
this is quite restrictive, users may stop using the XML namespace
support altogether to fallback on regular abstract bean definition for
the container.

This commit adds a concurrency attribute to the jms and jca listener
element as well as on the @JmsListener annotation. If the value is set,
it takes precedence; otherwise the value provided by the factory is
used.

Issue: SPR-11988
This commit is contained in:
Stephane Nicoll
2014-07-14 15:49:05 +02:00
parent 5e1a5c8d51
commit 29bdbceaa2
14 changed files with 140 additions and 9 deletions

View File

@@ -107,4 +107,13 @@ public @interface JmsListener {
*/
String selector() default "";
/**
* The concurrency for the listener, if any.
* <p>The concurrency limits can be a "lower-upper" String, e.g. "5-10", or a simple
* upper limit String, e.g. "10" (the lower limit will be 1 in this case).
* <p>The underlying container may or may not support all features. For instance, it
* may not be able to scale: in that case only the upper value is used.
*/
String concurrency() default "";
}

View File

@@ -181,6 +181,9 @@ public class JmsListenerAnnotationBeanPostProcessor implements BeanPostProcessor
if (StringUtils.hasText(jmsListener.subscription())) {
endpoint.setSubscription(jmsListener.subscription());
}
if (StringUtils.hasText(jmsListener.concurrency())) {
endpoint.setConcurrency(jmsListener.concurrency());
}
JmsListenerContainerFactory<?> factory = null;
String containerFactoryBeanName = jmsListener.containerFactory();

View File

@@ -42,6 +42,8 @@ public abstract class AbstractJmsListenerEndpoint implements JmsListenerEndpoint
private String selector;
private String concurrency;
public void setId(String id) {
this.id = id;
@@ -95,6 +97,23 @@ public abstract class AbstractJmsListenerEndpoint implements JmsListenerEndpoint
return this.selector;
}
/**
* Set a concurrency for the listener, if any.
* <p>The concurrency limits can be a "lower-upper" String, e.g. "5-10", or a simple
* upper limit String, e.g. "10" (the lower limit will be 1 in this case).
* <p>The underlying container may or may not support all features. For instance, it
* may not be able to scale: in that case only the upper value is used.
*/
public void setConcurrency(String concurrency) {
this.concurrency = concurrency;
}
/**
* Return the concurrency for the listener, if any.
*/
public String getConcurrency() {
return concurrency;
}
@Override
public void setupMessageContainer(MessageListenerContainer container) {
@@ -121,6 +140,9 @@ public abstract class AbstractJmsListenerEndpoint implements JmsListenerEndpoint
if (getSelector() != null) {
container.setMessageSelector(getSelector());
}
if (getConcurrency() != null) {
container.setConcurrency(getConcurrency());
}
setupMessageListener(container);
}
@@ -140,6 +162,9 @@ public abstract class AbstractJmsListenerEndpoint implements JmsListenerEndpoint
if (getSelector() != null) {
activationSpecConfig.setMessageSelector(getSelector());
}
if (getConcurrency() != null) {
activationSpecConfig.setConcurrency(getConcurrency());
}
setupMessageListener(container);
}

View File

@@ -247,6 +247,15 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
}
configDef.getPropertyValues().add("messageSelector", selector);
}
if (ele.hasAttribute(CONCURRENCY_ATTRIBUTE)) {
String concurrency = ele.getAttribute(CONCURRENCY_ATTRIBUTE);
if (!StringUtils.hasText(concurrency)) {
parserContext.getReaderContext().error(
"Listener 'concurrency' attribute contains empty value.", ele);
}
configDef.getPropertyValues().add("concurrency", concurrency);
}
}
protected PropertyValues parseCommonContainerProperties(Element ele, ParserContext parserContext) {

View File

@@ -70,10 +70,10 @@ class JcaListenerContainerParser extends AbstractListenerContainerParser {
containerDef.setSource(context.getSource());
containerDef.setBeanClassName("org.springframework.jms.listener.endpoint.JmsMessageEndpointManager");
containerDef.getPropertyValues().addPropertyValues(context.getContainerValues());
applyContainerValues(context, containerDef);
BeanDefinition activationSpec = getActivationSpecConfigBeanDefinition(context.getContainerValues());
BeanDefinition activationSpec = getActivationSpecConfigBeanDefinition(containerDef.getPropertyValues());
parseListenerConfiguration(context.getListenerElement(), context.getParserContext(), activationSpec);
String phase = context.getContainerElement().getAttribute(PHASE_ATTRIBUTE);
@@ -84,6 +84,21 @@ class JcaListenerContainerParser extends AbstractListenerContainerParser {
return containerDef;
}
/**
* The property values provided by the factory element contains a mutable property (the
* activation spec config). To avoid changing the bean definition from the parent, a clone
* bean definition is created for the container being configured.
*/
private void applyContainerValues(ListenerContainerParserContext context, RootBeanDefinition containerDef) {
// Apply settings from the container
containerDef.getPropertyValues().addPropertyValues(context.getContainerValues());
// Clone the activationSpecConfig property value as it is mutable
PropertyValue pv = containerDef.getPropertyValues().getPropertyValue("activationSpecConfig");
RootBeanDefinition activationSpecConfig = new RootBeanDefinition((RootBeanDefinition) pv.getValue());
containerDef.getPropertyValues().add("activationSpecConfig", activationSpecConfig);
}
@Override
protected boolean indicatesPubSub(PropertyValues propertyValues) {
BeanDefinition configDef = getActivationSpecConfigBeanDefinition(propertyValues);

View File

@@ -92,7 +92,6 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser {
// Set all container values
containerDef.getPropertyValues().addPropertyValues(context.getContainerValues());
parseListenerConfiguration(context.getListenerElement(), context.getParserContext(), containerDef);
Element containerEle = context.getContainerElement();
String containerType = containerEle.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
@@ -116,6 +115,9 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser {
containerDef.getPropertyValues().add("phase", phase);
}
// Parse listener specific settings
parseListenerConfiguration(context.getListenerElement(), context.getParserContext(), containerDef);
return containerDef;
}

View File

@@ -147,6 +147,11 @@ public abstract class AbstractMessageListenerContainer
private boolean acceptMessagesWhileStopping = false;
/**
* Specify concurrency limits.
*/
public abstract void setConcurrency(String concurrency);
/**
* Set the destination to receive messages from.
* <p>Alternatively, specify a "destinationName", to be dynamically

View File

@@ -295,6 +295,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
* ({@link #setConcurrentConsumers}) and will slowly scale up to the maximum number
* of consumers {@link #setMaxConcurrentConsumers} in case of increasing load.
*/
@Override
public void setConcurrency(String concurrency) {
try {
int separatorIndex = concurrency.indexOf('-');

View File

@@ -114,6 +114,7 @@ public class SimpleMessageListenerContainer extends AbstractMessageListenerConta
* {@link DefaultMessageListenerContainer}. For this local listener container,
* generally use {@link #setConcurrentConsumers} instead.
*/
@Override
public void setConcurrency(String concurrency) {
try {
int separatorIndex = concurrency.indexOf('-');