mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-09 08:40:39 +00:00
Introduce @ConcurrencyLimit annotation based on ConcurrencyThrottleInterceptor
Moves @Retryable infrastructure to resilience package in spring-context module. Includes duration parsing and placeholder resolution for @Retryable attributes. Provides convenient @EnableResilientMethods for @Retryable + @ConcurrencyLimit. Closes gh-35133 See gh-34529
This commit is contained in:
+1
-1
@@ -41,7 +41,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends AbstractAdvisingBeanPostProcessor
|
||||
implements BeanFactoryAware {
|
||||
|
||||
private @Nullable ConfigurableListableBeanFactory beanFactory;
|
||||
protected @Nullable ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
|
||||
@Override
|
||||
|
||||
+15
-1
@@ -44,10 +44,24 @@ import org.springframework.util.ConcurrencyThrottleSupport;
|
||||
public class ConcurrencyThrottleInterceptor extends ConcurrencyThrottleSupport
|
||||
implements MethodInterceptor, Serializable {
|
||||
|
||||
/**
|
||||
* Create a default {@code ConcurrencyThrottleInterceptor}
|
||||
* with concurrency limit 1.
|
||||
*/
|
||||
public ConcurrencyThrottleInterceptor() {
|
||||
setConcurrencyLimit(1);
|
||||
this(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default {@code ConcurrencyThrottleInterceptor}
|
||||
* with the given concurrency limit.
|
||||
* @since 7.0
|
||||
*/
|
||||
public ConcurrencyThrottleInterceptor(int concurrencyLimit) {
|
||||
setConcurrencyLimit(concurrencyLimit);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable Object invoke(MethodInvocation methodInvocation) throws Throwable {
|
||||
beforeAccess();
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* 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.aop.retry;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.retry.RetryException;
|
||||
import org.springframework.core.retry.RetryPolicy;
|
||||
import org.springframework.core.retry.RetryTemplate;
|
||||
import org.springframework.core.retry.Retryable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Abstract retry interceptor implementation, adapting a given
|
||||
* retry specification to either {@link RetryTemplate} or Reactor.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
* @see #getRetrySpec
|
||||
* @see RetryTemplate
|
||||
* @see Mono#retryWhen
|
||||
* @see Flux#retryWhen
|
||||
*/
|
||||
public abstract class AbstractRetryInterceptor implements MethodInterceptor {
|
||||
|
||||
/**
|
||||
* Reactive Streams API present on the classpath?
|
||||
*/
|
||||
private static final boolean reactiveStreamsPresent = ClassUtils.isPresent(
|
||||
"org.reactivestreams.Publisher", AbstractRetryInterceptor.class.getClassLoader());
|
||||
|
||||
private final @Nullable ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
|
||||
public AbstractRetryInterceptor() {
|
||||
if (reactiveStreamsPresent) {
|
||||
this.reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
|
||||
}
|
||||
else {
|
||||
this.reactiveAdapterRegistry = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
Method method = invocation.getMethod();
|
||||
Object target = invocation.getThis();
|
||||
MethodRetrySpec spec = getRetrySpec(method, (target != null ? target.getClass() : method.getDeclaringClass()));
|
||||
|
||||
if (spec == null) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
if (this.reactiveAdapterRegistry != null) {
|
||||
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
|
||||
if (adapter != null) {
|
||||
Object result = invocation.proceed();
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
return ReactorDelegate.adaptReactiveResult(result, adapter, spec, method);
|
||||
}
|
||||
}
|
||||
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder()
|
||||
.includes(spec.includes())
|
||||
.excludes(spec.excludes())
|
||||
.predicate(spec.predicate().forMethod(method))
|
||||
.maxAttempts(spec.maxAttempts())
|
||||
.delay(spec.delay())
|
||||
.jitter(spec.jitter())
|
||||
.multiplier(spec.multiplier())
|
||||
.maxDelay(spec.maxDelay())
|
||||
.build();
|
||||
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
try {
|
||||
return retryTemplate.execute(new Retryable<>() {
|
||||
@Override
|
||||
public @Nullable Object execute() throws Throwable {
|
||||
return invocation.proceed();
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return ClassUtils.getQualifiedMethodName(method, (target != null ? target.getClass() : null));
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (RetryException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
throw (cause != null ? cause : new IllegalStateException(ex.getMessage(), ex));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the retry specification for the given method on the given target.
|
||||
* @param method the currently executing method
|
||||
* @param targetClass the class of the current target object
|
||||
* @return the retry specification as a {@link MethodRetrySpec}
|
||||
*/
|
||||
protected abstract @Nullable MethodRetrySpec getRetrySpec(Method method, Class<?> targetClass);
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on Reactive Streams and Reactor at runtime.
|
||||
*/
|
||||
private static class ReactorDelegate {
|
||||
|
||||
public static Object adaptReactiveResult(
|
||||
Object result, ReactiveAdapter adapter, MethodRetrySpec spec, Method method) {
|
||||
|
||||
Publisher<?> publisher = adapter.toPublisher(result);
|
||||
Retry retry = Retry.backoff(spec.maxAttempts(), spec.delay())
|
||||
.jitter(
|
||||
spec.delay().isZero() ? 0.0 :
|
||||
Math.max(0.0, Math.min(1.0, spec.jitter().toNanos() / (double) spec.delay().toNanos()))
|
||||
)
|
||||
.multiplier(spec.multiplier())
|
||||
.maxBackoff(spec.maxDelay())
|
||||
.filter(spec.combinedPredicate().forMethod(method));
|
||||
publisher = (adapter.isMultiValue() ? Flux.from(publisher).retryWhen(retry) :
|
||||
Mono.from(publisher).retryWhen(retry));
|
||||
return adapter.fromPublisher(publisher);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.aop.retry;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Predicate for retrying a {@link Throwable} from a specific {@link Method}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
* @see MethodRetrySpec#predicate()
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MethodRetryPredicate {
|
||||
|
||||
/**
|
||||
* Determine whether the given {@code Method} should be retried after
|
||||
* throwing the given {@code Throwable}.
|
||||
* @param method the method to potentially retry
|
||||
* @param throwable the exception encountered
|
||||
*/
|
||||
boolean shouldRetry(Method method, Throwable throwable);
|
||||
|
||||
/**
|
||||
* Build a {@code Predicate} for testing exceptions from a given method.
|
||||
* @param method the method to build a predicate for
|
||||
*/
|
||||
default Predicate<Throwable> forMethod(Method method) {
|
||||
return (t -> shouldRetry(method, t));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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.aop.retry;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* A specification for retry attempts on a given method, combining common
|
||||
* retry characteristics. This roughly matches the annotation attributes
|
||||
* on {@link org.springframework.aop.retry.annotation.Retryable}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
* @param includes applicable exception types to attempt a retry for
|
||||
* @param excludes non-applicable exception types to avoid a retry for
|
||||
* @param predicate a predicate for filtering exceptions from applicable methods
|
||||
* @param maxAttempts the maximum number of retry attempts
|
||||
* @param delay the base delay after the initial invocation
|
||||
* @param jitter a jitter value for the next retry attempt
|
||||
* @param multiplier a multiplier for a delay for the next retry attempt
|
||||
* @param maxDelay the maximum delay for any retry attempt
|
||||
* @see AbstractRetryInterceptor#getRetrySpec
|
||||
* @see SimpleRetryInterceptor#SimpleRetryInterceptor(MethodRetrySpec)
|
||||
* @see org.springframework.aop.retry.annotation.Retryable
|
||||
*/
|
||||
public record MethodRetrySpec(
|
||||
Collection<Class<? extends Throwable>> includes,
|
||||
Collection<Class<? extends Throwable>> excludes,
|
||||
MethodRetryPredicate predicate,
|
||||
long maxAttempts,
|
||||
Duration delay,
|
||||
Duration jitter,
|
||||
double multiplier,
|
||||
Duration maxDelay) {
|
||||
|
||||
public MethodRetrySpec(MethodRetryPredicate predicate, long maxAttempts, Duration delay) {
|
||||
this(predicate, maxAttempts, delay, Duration.ofMillis(0), 1.0, Duration.ofMillis(Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
public MethodRetrySpec(MethodRetryPredicate predicate, long maxAttempts, Duration delay,
|
||||
Duration jitter, double multiplier, Duration maxDelay) {
|
||||
|
||||
this(Collections.emptyList(), Collections.emptyList(), predicate, maxAttempts, delay,
|
||||
jitter, multiplier, maxDelay);
|
||||
}
|
||||
|
||||
|
||||
MethodRetryPredicate combinedPredicate() {
|
||||
return (method, throwable) -> {
|
||||
if (!this.excludes.isEmpty()) {
|
||||
for (Class<? extends Throwable> exclude : this.excludes) {
|
||||
if (exclude.isInstance(throwable)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.includes.isEmpty()) {
|
||||
boolean included = false;
|
||||
for (Class<? extends Throwable> include : this.includes) {
|
||||
if (include.isInstance(throwable)) {
|
||||
included = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!included) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.predicate.shouldRetry(method, throwable);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* 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.aop.retry;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A simple concrete retry interceptor based on a given {@link MethodRetrySpec}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
*/
|
||||
public class SimpleRetryInterceptor extends AbstractRetryInterceptor {
|
||||
|
||||
private final MethodRetrySpec retrySpec;
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@code SimpleRetryInterceptor} for the given {@link MethodRetrySpec}.
|
||||
* @param retrySpec the specification to use for all method invocations
|
||||
*/
|
||||
public SimpleRetryInterceptor(MethodRetrySpec retrySpec) {
|
||||
this.retrySpec = retrySpec;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MethodRetrySpec getRetrySpec(Method method, Class<?> targetClass) {
|
||||
return this.retrySpec;
|
||||
}
|
||||
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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.aop.retry.annotation;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
|
||||
import org.springframework.aop.support.ComposablePointcut;
|
||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
||||
|
||||
/**
|
||||
* A convenient {@link org.springframework.beans.factory.config.BeanPostProcessor
|
||||
* BeanPostProcessor} that applies {@link RetryAnnotationInterceptor}
|
||||
* to all bean methods annotated with {@link Retryable} annotations.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class RetryAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
|
||||
|
||||
public RetryAnnotationBeanPostProcessor() {
|
||||
setBeforeExistingAdvisors(true);
|
||||
|
||||
Pointcut cpc = new AnnotationMatchingPointcut(Retryable.class, true);
|
||||
Pointcut mpc = new AnnotationMatchingPointcut(null, Retryable.class, true);
|
||||
this.advisor = new DefaultPointcutAdvisor(
|
||||
new ComposablePointcut(cpc).union(mpc),
|
||||
new RetryAnnotationInterceptor());
|
||||
}
|
||||
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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.aop.retry.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.retry.AbstractRetryInterceptor;
|
||||
import org.springframework.aop.retry.MethodRetryPredicate;
|
||||
import org.springframework.aop.retry.MethodRetrySpec;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* An annotation-based retry interceptor based on {@link Retryable} annotations.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
*/
|
||||
public class RetryAnnotationInterceptor extends AbstractRetryInterceptor {
|
||||
|
||||
private final Map<MethodClassKey, MethodRetrySpec> retrySpecCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@Override
|
||||
protected @Nullable MethodRetrySpec getRetrySpec(Method method, Class<?> targetClass) {
|
||||
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
|
||||
MethodRetrySpec retrySpec = this.retrySpecCache.get(cacheKey);
|
||||
if (retrySpec != null) {
|
||||
return retrySpec;
|
||||
}
|
||||
|
||||
Retryable retryable = AnnotatedElementUtils.getMergedAnnotation(method, Retryable.class);
|
||||
if (retryable == null) {
|
||||
retryable = AnnotatedElementUtils.getMergedAnnotation(targetClass, Retryable.class);
|
||||
if (retryable == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
TimeUnit timeUnit = retryable.timeUnit();
|
||||
retrySpec = new MethodRetrySpec(
|
||||
Arrays.asList(retryable.includes()), Arrays.asList(retryable.excludes()),
|
||||
instantiatePredicate(retryable.predicate()), retryable.maxAttempts(),
|
||||
toDuration(retryable.delay(), timeUnit), toDuration(retryable.jitter(), timeUnit),
|
||||
retryable.multiplier(), toDuration(retryable.maxDelay(), timeUnit));
|
||||
|
||||
MethodRetrySpec existing = this.retrySpecCache.putIfAbsent(cacheKey, retrySpec);
|
||||
return (existing != null ? existing : retrySpec);
|
||||
}
|
||||
|
||||
|
||||
private static MethodRetryPredicate instantiatePredicate(Class<? extends MethodRetryPredicate> predicateClass) {
|
||||
if (predicateClass == MethodRetryPredicate.class) {
|
||||
return (method, throwable) -> true;
|
||||
}
|
||||
try {
|
||||
return ReflectionUtils.accessibleConstructor(predicateClass).newInstance();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException("Failed to instantiate predicate class [" + predicateClass + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Duration toDuration(long value, TimeUnit timeUnit) {
|
||||
try {
|
||||
return Duration.of(value, timeUnit.toChronoUnit());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported unit " + timeUnit + " for value \"" + value + "\": " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* 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.aop.retry.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.aop.retry.MethodRetryPredicate;
|
||||
import org.springframework.aot.hint.annotation.Reflective;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* A common annotation specifying retry characteristics for an individual method,
|
||||
* or for all proxy-invoked methods in a given class hierarchy if annotated at
|
||||
* the type level.
|
||||
*
|
||||
* <p>Aligned with {@link org.springframework.core.retry.RetryTemplate}
|
||||
* as well as Reactor's retry support, either re-invoking an imperative
|
||||
* target method or decorating a reactive result accordingly.
|
||||
*
|
||||
* <p>Inspired by the <a href="https://github.com/spring-projects/spring-retry">Spring Retry</a>
|
||||
* project but redesigned as a minimal core retry feature in the Spring Framework.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
* @see RetryAnnotationBeanPostProcessor
|
||||
* @see RetryAnnotationInterceptor
|
||||
* @see org.springframework.core.retry.RetryPolicy
|
||||
* @see org.springframework.core.retry.RetryTemplate
|
||||
* @see reactor.core.publisher.Mono#retryWhen
|
||||
* @see reactor.core.publisher.Flux#retryWhen
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Reflective
|
||||
public @interface Retryable {
|
||||
|
||||
/**
|
||||
* Convenient default attribute for {@link #includes()},
|
||||
* typically used with a single exception type to retry for.
|
||||
*/
|
||||
@AliasFor("includes")
|
||||
Class<? extends Throwable>[] value() default {};
|
||||
|
||||
/**
|
||||
* Applicable exception types to attempt a retry for. This attribute
|
||||
* allows for the convenient specification of assignable exception types.
|
||||
* <p>The default is empty, leading to a retry attempt for any exception.
|
||||
* @see #excludes()
|
||||
* @see #predicate()
|
||||
*/
|
||||
@AliasFor("value")
|
||||
Class<? extends Throwable>[] includes() default {};
|
||||
|
||||
/**
|
||||
* Non-applicable exception types to avoid a retry for. This attribute
|
||||
* allows for the convenient specification of assignable exception types.
|
||||
* <p>The default is empty, leading to a retry attempt for any exception.
|
||||
* @see #includes()
|
||||
* @see #predicate()
|
||||
*/
|
||||
Class<? extends Throwable>[] excludes() default {};
|
||||
|
||||
/**
|
||||
* A predicate for filtering applicable exceptions for which
|
||||
* an invocation can be retried.
|
||||
* <p>The default is a retry attempt for any exception.
|
||||
* @see #includes()
|
||||
* @see #excludes()
|
||||
*/
|
||||
Class<? extends MethodRetryPredicate> predicate() default MethodRetryPredicate.class;
|
||||
|
||||
/**
|
||||
* The maximum number of retry attempts, in addition to the initial invocation.
|
||||
* <p>The default is 3.
|
||||
*/
|
||||
long maxAttempts() default 3;
|
||||
|
||||
/**
|
||||
* The base delay after the initial invocation. If a multiplier is specified,
|
||||
* this serves as the initial delay to multiply from.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>The default is 1000.
|
||||
* @see #jitter()
|
||||
* @see #multiplier()
|
||||
* @see #maxDelay()
|
||||
*/
|
||||
long delay() default 1000;
|
||||
|
||||
/**
|
||||
* A jitter value for the base retry attempt, randomly subtracted or added to
|
||||
* the calculated delay, resulting in a value between {@code delay - jitter}
|
||||
* and {@code delay + jitter} but never below the base {@link #delay()} or
|
||||
* above {@link #maxDelay()}. If a multiplier is specified, it is applied
|
||||
* to the jitter value as well.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>The default is 0 (no jitter).
|
||||
* @see #delay()
|
||||
* @see #multiplier()
|
||||
* @see #maxDelay()
|
||||
*/
|
||||
long jitter() default 0;
|
||||
|
||||
/**
|
||||
* A multiplier for a delay for the next retry attempt, applied
|
||||
* to the previous delay (starting with {@link #delay()}) as well
|
||||
* as to the applicable {@link #jitter()} for each attempt.
|
||||
* <p>The default is 1.0, effectively resulting in a fixed delay.
|
||||
* @see #delay()
|
||||
* @see #jitter()
|
||||
* @see #maxDelay()
|
||||
*/
|
||||
double multiplier() default 1.0;
|
||||
|
||||
/**
|
||||
* The maximum delay for any retry attempt, limiting how far {@link #jitter()}
|
||||
* and {@link #multiplier()} can increase the {@linkplain #delay() delay}.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>The default is unlimited.
|
||||
* @see #delay()
|
||||
* @see #jitter()
|
||||
* @see #multiplier()
|
||||
*/
|
||||
long maxDelay() default Long.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* The {@link TimeUnit} to use for {@link #delay}, {@link #jitter},
|
||||
* and {@link #maxDelay}.
|
||||
* <p>The default is {@link TimeUnit#MILLISECONDS}.
|
||||
*/
|
||||
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Annotation-based retry support for common Spring setups.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.aop.retry.annotation;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* A retry interceptor arrangement based on {@code core.retry} and Reactor.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.aop.retry;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
Reference in New Issue
Block a user