Add placeholder resolution support for @⁠ConcurrencyLimit

See gh-35461
Closes gh-35470

Signed-off-by: Hyunsang Han <gustkd3@gmail.com>
This commit is contained in:
Hyunsang Han
2025-09-13 00:15:57 +09:00
committed by Sam Brannen
parent 23b8c61b99
commit 91f112a918
3 changed files with 90 additions and 3 deletions
@@ -42,6 +42,7 @@ import org.springframework.aot.hint.annotation.Reflective;
* {@link org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor}.
*
* @author Juergen Hoeller
* @author Hyunsang Han
* @since 7.0
* @see EnableResilientMethods
* @see ConcurrencyLimitBeanPostProcessor
@@ -62,4 +63,12 @@ public @interface ConcurrencyLimit {
*/
int value() default 1;
/**
* The concurrency limit as a configurable String.
* A non-empty value specified here overrides the {@link #value()} attribute.
* <p>This supports Spring-style "${...}" placeholders as well as SpEL expressions.
* @see #value()
*/
String valueString() default "";
}
@@ -31,9 +31,12 @@ import org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
/**
* A convenient {@link org.springframework.beans.factory.config.BeanPostProcessor
@@ -41,10 +44,14 @@ import org.springframework.util.ConcurrentReferenceHashMap;
* annotated with {@link ConcurrencyLimit @ConcurrencyLimit}.
*
* @author Juergen Hoeller
* @author Hyunsang Han
* @since 7.0
*/
@SuppressWarnings("serial")
public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements EmbeddedValueResolverAware {
private @Nullable StringValueResolver embeddedValueResolver;
public ConcurrencyLimitBeanPostProcessor() {
setBeforeExistingAdvisors(true);
@@ -57,7 +64,13 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
}
private static class ConcurrencyLimitInterceptor implements MethodInterceptor {
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
private class ConcurrencyLimitInterceptor implements MethodInterceptor {
private final Map<Object, ConcurrencyThrottleCache> cachePerInstance =
new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);
@@ -93,7 +106,8 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
}
if (interceptor == null) {
Assert.state(limit != null, "No @ConcurrencyLimit annotation found");
interceptor = new ConcurrencyThrottleInterceptor(limit.value());
int concurrencyLimit = parseInt(limit.value(), limit.valueString());
interceptor = new ConcurrencyThrottleInterceptor(concurrencyLimit);
if (!perMethod) {
cache.classInterceptor = interceptor;
}
@@ -104,6 +118,18 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
}
return interceptor.invoke(invocation);
}
private int parseInt(int value, String stringValue) {
if (StringUtils.hasText(stringValue)) {
if (embeddedValueResolver != null) {
stringValue = embeddedValueResolver.resolveStringValue(stringValue);
}
if (StringUtils.hasText(stringValue)) {
return Integer.parseInt(stringValue);
}
}
return value;
}
}