mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Add configurable throttle policy to @ConcurrencyLimit annotation
Closes gh-36109
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.resilience;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
/**
|
||||
* Exception thrown when a target will not get invoked due to a resilience policy,
|
||||
* such as the concurrency limit having been reached for a class/method annotated with
|
||||
* {@link org.springframework.resilience.annotation.ConcurrencyLimit @ConcurrencyLimit}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0.3
|
||||
* @see org.springframework.resilience.annotation.ConcurrencyLimit#rejectOnExcess()
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class InvocationRejectedException extends RejectedExecutionException {
|
||||
|
||||
private final Object target;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code InvocationRejectedException}
|
||||
* with the specified detail message and target instance.
|
||||
* @param msg the detail message
|
||||
* @param target the target instance that was about to be invoked
|
||||
*/
|
||||
public InvocationRejectedException(String msg, Object target) {
|
||||
super(msg);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the target instance that was about to be invoked.
|
||||
*/
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
}
|
||||
+34
-1
@@ -28,7 +28,9 @@ import org.springframework.core.annotation.AliasFor;
|
||||
/**
|
||||
* A common annotation specifying a concurrency limit for an individual method,
|
||||
* or for all proxy-invoked methods in a given class hierarchy if annotated at
|
||||
* the type level.
|
||||
* the type level. The default behavior is to block further method invocations
|
||||
* when the limit has been reached. Alternatively, further invocations can be
|
||||
* rejected through configuring {@link #policy()} as {@code policy = REJECT}.
|
||||
*
|
||||
* <p>In the type-level case, all methods inheriting the concurrency limit
|
||||
* from the type level share a common concurrency throttle, with any mix
|
||||
@@ -95,4 +97,35 @@ public @interface ConcurrencyLimit {
|
||||
*/
|
||||
String limitString() default "";
|
||||
|
||||
/**
|
||||
* The policy for throttling method invocations when the limit has been reached.
|
||||
* <p>The default behavior is to block further concurrent invocations once the
|
||||
* specified limit has been reached: {@link ThrottlePolicy#BLOCK}.
|
||||
* <p>Switch this policy to {@code REJECT} for rejecting further invocations instead,
|
||||
* throwing {@link org.springframework.resilience.InvocationRejectedException}
|
||||
* (which extends the common {@link java.util.concurrent.RejectedExecutionException})
|
||||
* on any further concurrent invocation attempts: {@link ThrottlePolicy#REJECT}.
|
||||
* @since 7.0.3
|
||||
*/
|
||||
ThrottlePolicy policy() default ThrottlePolicy.BLOCK;
|
||||
|
||||
|
||||
/**
|
||||
* Policy to apply for throttling method invocations when the limit has been reached.
|
||||
* @since 7.0.3
|
||||
*/
|
||||
enum ThrottlePolicy {
|
||||
|
||||
/**
|
||||
* The default: block until we can invoke the method within the configured limit.
|
||||
*/
|
||||
BLOCK,
|
||||
|
||||
/**
|
||||
* Alternative: reject further method invocations once the limit has been reached.
|
||||
* @see org.springframework.resilience.InvocationRejectedException
|
||||
*/
|
||||
REJECT
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+27
-1
@@ -35,7 +35,9 @@ 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.resilience.InvocationRejectedException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
|
||||
@@ -115,7 +117,11 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
|
||||
if (concurrencyLimit < -1) {
|
||||
throw new IllegalStateException(annotation + " must be configured with a valid limit");
|
||||
}
|
||||
interceptor = new ConcurrencyThrottleInterceptor(concurrencyLimit);
|
||||
interceptor = (annotation.policy() == ConcurrencyLimit.ThrottlePolicy.REJECT ?
|
||||
new RejectingConcurrencyThrottleInterceptor(concurrencyLimit,
|
||||
(perMethod ? ClassUtils.getQualifiedMethodName(method) : targetClass.getName()),
|
||||
instance) :
|
||||
new ConcurrencyThrottleInterceptor(concurrencyLimit));
|
||||
if (!perMethod) {
|
||||
holder.classInterceptor = interceptor;
|
||||
}
|
||||
@@ -148,4 +154,24 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
|
||||
@Nullable MethodInterceptor classInterceptor;
|
||||
}
|
||||
|
||||
|
||||
private static class RejectingConcurrencyThrottleInterceptor extends ConcurrencyThrottleInterceptor {
|
||||
|
||||
private final String identifier;
|
||||
|
||||
private final Object target;
|
||||
|
||||
public RejectingConcurrencyThrottleInterceptor(int concurrencyLimit, String identifier, Object target) {
|
||||
super(concurrencyLimit);
|
||||
this.identifier = identifier;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLimitReached() {
|
||||
throw new InvocationRejectedException(
|
||||
"Concurrency limit reached for " + this.identifier + ": " + getConcurrencyLimit(), this.target);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Common exceptions thrown by Spring's resilience facilities.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.resilience;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
Reference in New Issue
Block a user