mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
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:
+20
-5
@@ -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 — 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 "";
|
||||
|
||||
}
|
||||
|
||||
+6
-5
@@ -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;
|
||||
|
||||
+7
-9
@@ -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<CompletableFuture<?>> futures = new ArrayList<>(10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
futures.add(CompletableFuture.runAsync(proxy::concurrentOperation));
|
||||
@@ -125,6 +122,7 @@ class ConcurrencyLimitTests {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
|
||||
static class NonAnnotatedBean {
|
||||
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
@@ -197,7 +195,7 @@ class ConcurrencyLimitTests {
|
||||
current.decrementAndGet();
|
||||
}
|
||||
|
||||
@ConcurrencyLimit(1)
|
||||
@ConcurrencyLimit(limit = 1)
|
||||
public void overrideOperation() {
|
||||
if (currentOverride.incrementAndGet() > 1) {
|
||||
throw new IllegalStateException();
|
||||
@@ -222,7 +220,7 @@ class ConcurrencyLimitTests {
|
||||
|
||||
AtomicInteger current = new AtomicInteger();
|
||||
|
||||
@ConcurrencyLimit(valueString = "${test.concurrency.limit}")
|
||||
@ConcurrencyLimit(limitString = "${test.concurrency.limit}")
|
||||
public void concurrentOperation() {
|
||||
if (current.incrementAndGet() > 3) { // Assumes test.concurrency.limit=3
|
||||
throw new IllegalStateException();
|
||||
|
||||
Reference in New Issue
Block a user