Introduce @⁠ConcurrencyLimit(limit) and revise contribution

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
This commit is contained in:
Sam Brannen
2025-09-22 16:02:31 +02:00
parent 91f112a918
commit 73ad1ebffb
3 changed files with 33 additions and 19 deletions
@@ -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()}.
* <p>Intended to be used when no other attributes are needed &mdash; 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.
* <p>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.
* <p>A non-empty value specified here overrides the {@link #limit()} (or
* {@link #value()}) attribute.
* <p>This supports Spring-style "${...}" placeholders as well as SpEL expressions.
* @see #value()
* @see #limit()
*/
String valueString() default "";
String limitString() default "";
}
@@ -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;