From 73ad1ebffb2c10e57c812919b0afb0aba13cc940 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:02:31 +0200 Subject: [PATCH] =?UTF-8?q?Introduce=20@=E2=81=A0ConcurrencyLimit(limit)?= =?UTF-8?q?=20and=20revise=20contribution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces a new `limit` attribute in @ConcurrencyLimit as an alias for the existing `value` attribute. This commit also renames the `valueString` attribute to `limitString`. See gh-35461 See gh-35470 --- .../annotation/ConcurrencyLimit.java | 25 +++++++++++++++---- .../ConcurrencyLimitBeanPostProcessor.java | 11 ++++---- .../resilience/ConcurrencyLimitTests.java | 16 ++++++------ 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/resilience/annotation/ConcurrencyLimit.java b/spring-context/src/main/java/org/springframework/resilience/annotation/ConcurrencyLimit.java index d042b5c1d6a..bacc60a5756 100644 --- a/spring-context/src/main/java/org/springframework/resilience/annotation/ConcurrencyLimit.java +++ b/spring-context/src/main/java/org/springframework/resilience/annotation/ConcurrencyLimit.java @@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.core.annotation.AliasFor; /** * A common annotation specifying a concurrency limit for an individual method, @@ -43,6 +44,7 @@ import org.springframework.aot.hint.annotation.Reflective; * * @author Juergen Hoeller * @author Hyunsang Han + * @author Sam Brannen * @since 7.0 * @see EnableResilientMethods * @see ConcurrencyLimitBeanPostProcessor @@ -55,20 +57,33 @@ import org.springframework.aot.hint.annotation.Reflective; @Reflective public @interface ConcurrencyLimit { + /** + * Alias for {@link #limit()}. + *
Intended to be used when no other attributes are needed — for + * example, {@code @ConcurrencyLimit(5)}. + * @see #limitString() + */ + @AliasFor("limit") + int value() default 1; + /** * The applicable concurrency limit: 1 by default, * effectively locking the target instance for each method invocation. *
Specify a limit higher than 1 for pool-like throttling, constraining * the number of concurrent invocations similar to the upper bound of a pool. + * @see #value() + * @see #limitString() */ - int value() default 1; + @AliasFor("value") + int limit() default 1; /** - * The concurrency limit as a configurable String. - * A non-empty value specified here overrides the {@link #value()} attribute. + * The concurrency limit, as a configurable String. + *
A non-empty value specified here overrides the {@link #limit()} (or + * {@link #value()}) attribute. *
This supports Spring-style "${...}" placeholders as well as SpEL expressions.
- * @see #value()
+ * @see #limit()
*/
- String valueString() default "";
+ String limitString() default "";
}
diff --git a/spring-context/src/main/java/org/springframework/resilience/annotation/ConcurrencyLimitBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/resilience/annotation/ConcurrencyLimitBeanPostProcessor.java
index 2f133cb65f2..e195bd8d6f3 100644
--- a/spring-context/src/main/java/org/springframework/resilience/annotation/ConcurrencyLimitBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/resilience/annotation/ConcurrencyLimitBeanPostProcessor.java
@@ -53,6 +53,7 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
private @Nullable StringValueResolver embeddedValueResolver;
+
public ConcurrencyLimitBeanPostProcessor() {
setBeforeExistingAdvisors(true);
@@ -94,19 +95,19 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
interceptor = cache.methodInterceptors.get(method);
if (interceptor == null) {
boolean perMethod = false;
- ConcurrencyLimit limit = AnnotatedElementUtils.getMergedAnnotation(method, ConcurrencyLimit.class);
- if (limit != null) {
+ ConcurrencyLimit annotation = AnnotatedElementUtils.getMergedAnnotation(method, ConcurrencyLimit.class);
+ if (annotation != null) {
perMethod = true;
}
else {
interceptor = cache.classInterceptor;
if (interceptor == null) {
- limit = AnnotatedElementUtils.getMergedAnnotation(targetClass, ConcurrencyLimit.class);
+ annotation = AnnotatedElementUtils.getMergedAnnotation(targetClass, ConcurrencyLimit.class);
}
}
if (interceptor == null) {
- Assert.state(limit != null, "No @ConcurrencyLimit annotation found");
- int concurrencyLimit = parseInt(limit.value(), limit.valueString());
+ Assert.state(annotation != null, "No @ConcurrencyLimit annotation found");
+ int concurrencyLimit = parseInt(annotation.limit(), annotation.limitString());
interceptor = new ConcurrencyThrottleInterceptor(concurrencyLimit);
if (!perMethod) {
cache.classInterceptor = interceptor;
diff --git a/spring-context/src/test/java/org/springframework/resilience/ConcurrencyLimitTests.java b/spring-context/src/test/java/org/springframework/resilience/ConcurrencyLimitTests.java
index 26ca0645e20..d8c4c7c3488 100644
--- a/spring-context/src/test/java/org/springframework/resilience/ConcurrencyLimitTests.java
+++ b/spring-context/src/test/java/org/springframework/resilience/ConcurrencyLimitTests.java
@@ -18,7 +18,6 @@ package org.springframework.resilience;
import java.util.ArrayList;
import java.util.List;
-import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
@@ -30,7 +29,7 @@ import org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.testfixture.env.MockPropertySource;
import org.springframework.resilience.annotation.ConcurrencyLimit;
import org.springframework.resilience.annotation.ConcurrencyLimitBeanPostProcessor;
import org.springframework.resilience.annotation.EnableResilientMethods;
@@ -104,18 +103,16 @@ class ConcurrencyLimitTests {
@Test
void withPlaceholderResolution() {
- Properties props = new Properties();
- props.setProperty("test.concurrency.limit", "3");
-
+ MockPropertySource mockPropertySource = new MockPropertySource("test").withProperty("test.concurrency.limit", "3");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
- ctx.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("test", props));
+ ctx.getEnvironment().getPropertySources().addFirst(mockPropertySource);
ctx.register(PlaceholderTestConfig.class, PlaceholderBean.class);
ctx.refresh();
PlaceholderBean proxy = ctx.getBean(PlaceholderBean.class);
PlaceholderBean target = (PlaceholderBean) AopProxyUtils.getSingletonTarget(proxy);
- // Test with limit=3 from properties
+ // Test with limit=3 from MockPropertySource
List