Consistent guard for Reactive Streams presence
Closes gh-30707
This commit is contained in:
parent
24ddceea47
commit
18c6aceee7
|
@ -74,6 +74,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
import org.springframework.scheduling.support.CronTrigger;
|
import org.springframework.scheduling.support.CronTrigger;
|
||||||
import org.springframework.scheduling.support.ScheduledMethodRunnable;
|
import org.springframework.scheduling.support.ScheduledMethodRunnable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.StringValueResolver;
|
import org.springframework.util.StringValueResolver;
|
||||||
|
|
||||||
|
@ -122,6 +123,12 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||||
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
|
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reactive Streams API present on the classpath?
|
||||||
|
*/
|
||||||
|
private static final boolean reactiveStreamsPresent = ClassUtils.isPresent(
|
||||||
|
"org.reactivestreams.Publisher", ScheduledAnnotationBeanPostProcessor.class.getClassLoader());
|
||||||
|
|
||||||
protected final Log logger = LogFactory.getLog(getClass());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private final ScheduledTaskRegistrar registrar;
|
private final ScheduledTaskRegistrar registrar;
|
||||||
|
@ -402,13 +409,63 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||||
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
|
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
|
||||||
// Is the method a Kotlin suspending function? Throws if true and the reactor bridge isn't on the classpath.
|
// Is the method a Kotlin suspending function? Throws if true and the reactor bridge isn't on the classpath.
|
||||||
// Does the method return a reactive type? Throws if true and it isn't a deferred Publisher type.
|
// Does the method return a reactive type? Throws if true and it isn't a deferred Publisher type.
|
||||||
if (ScheduledAnnotationReactiveSupport.isReactive(method)) {
|
if (reactiveStreamsPresent && ScheduledAnnotationReactiveSupport.isReactive(method)) {
|
||||||
processScheduledAsync(scheduled, method, bean);
|
processScheduledAsync(scheduled, method, bean);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
processScheduledSync(scheduled, method, bean);
|
processScheduledSync(scheduled, method, bean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the given {@code @Scheduled} method declaration on the given bean,
|
||||||
|
* as a synchronous method. The method must accept no arguments. Its return value
|
||||||
|
* is ignored (if any), and the scheduled invocations of the method take place
|
||||||
|
* using the underlying {@link TaskScheduler} infrastructure.
|
||||||
|
* @param scheduled the {@code @Scheduled} annotation
|
||||||
|
* @param method the method that the annotation has been declared on
|
||||||
|
* @param bean the target bean instance
|
||||||
|
* @see #createRunnable(Object, Method)
|
||||||
|
*/
|
||||||
|
private void processScheduledSync(Scheduled scheduled, Method method, Object bean) {
|
||||||
|
Runnable task;
|
||||||
|
try {
|
||||||
|
task = createRunnable(bean, method);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException ex) {
|
||||||
|
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
|
||||||
|
method.getName() + "': " + ex.getMessage());
|
||||||
|
}
|
||||||
|
processScheduledTask(scheduled, task, method, bean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the given {@code @Scheduled} bean method declaration which returns
|
||||||
|
* a {@code Publisher}, or the given Kotlin suspending function converted to a
|
||||||
|
* {@code Publisher}. A {@code Runnable} which subscribes to that publisher is
|
||||||
|
* then repeatedly scheduled according to the annotation configuration.
|
||||||
|
* <p>Note that for fixed delay configuration, the subscription is turned into a blocking
|
||||||
|
* call instead. Types for which a {@code ReactiveAdapter} is registered but which cannot
|
||||||
|
* be deferred (i.e. not a {@code Publisher}) are not supported.
|
||||||
|
* @param scheduled the {@code @Scheduled} annotation
|
||||||
|
* @param method the method that the annotation has been declared on, which
|
||||||
|
* must either return a Publisher-adaptable type or be a Kotlin suspending function
|
||||||
|
* @param bean the target bean instance
|
||||||
|
* @see ScheduledAnnotationReactiveSupport
|
||||||
|
*/
|
||||||
|
private void processScheduledAsync(Scheduled scheduled, Method method, Object bean) {
|
||||||
|
Runnable task;
|
||||||
|
try {
|
||||||
|
task = ScheduledAnnotationReactiveSupport.createSubscriptionRunnable(method, bean, scheduled,
|
||||||
|
this.registrar::getObservationRegistry,
|
||||||
|
this.reactiveSubscriptions.computeIfAbsent(bean, k -> new CopyOnWriteArrayList<>()));
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException ex) {
|
||||||
|
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
|
||||||
|
method.getName() + "': " + ex.getMessage());
|
||||||
|
}
|
||||||
|
processScheduledTask(scheduled, task, method, bean);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the {@code Scheduled} annotation and schedule the provided {@code Runnable}
|
* Parse the {@code Scheduled} annotation and schedule the provided {@code Runnable}
|
||||||
* accordingly. The Runnable can represent either a synchronous method invocation
|
* accordingly. The Runnable can represent either a synchronous method invocation
|
||||||
|
@ -419,7 +476,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||||
* @param method the method that the annotation has been declared on
|
* @param method the method that the annotation has been declared on
|
||||||
* @param bean the target bean instance
|
* @param bean the target bean instance
|
||||||
*/
|
*/
|
||||||
protected void processScheduledTask(Scheduled scheduled, Runnable runnable, Method method, Object bean) {
|
private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method method, Object bean) {
|
||||||
try {
|
try {
|
||||||
boolean processedSchedule = false;
|
boolean processedSchedule = false;
|
||||||
String errorMessage =
|
String errorMessage =
|
||||||
|
@ -543,54 +600,6 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the given {@code @Scheduled} method declaration on the given bean,
|
|
||||||
* as a synchronous method. The method must accept no arguments. Its return value
|
|
||||||
* is ignored (if any), and the scheduled invocations of the method take place
|
|
||||||
* using the underlying {@link TaskScheduler} infrastructure.
|
|
||||||
* @param scheduled the {@code @Scheduled} annotation
|
|
||||||
* @param method the method that the annotation has been declared on
|
|
||||||
* @param bean the target bean instance
|
|
||||||
* @see #createRunnable(Object, Method)
|
|
||||||
*/
|
|
||||||
protected void processScheduledSync(Scheduled scheduled, Method method, Object bean) {
|
|
||||||
Runnable task;
|
|
||||||
try {
|
|
||||||
task = createRunnable(bean, method);
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException ex) {
|
|
||||||
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" + method.getName() + "': " + ex.getMessage());
|
|
||||||
}
|
|
||||||
processScheduledTask(scheduled, task, method, bean);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the given {@code @Scheduled} bean method declaration which returns
|
|
||||||
* a {@code Publisher}, or the given Kotlin suspending function converted to a
|
|
||||||
* {@code Publisher}. A {@code Runnable} which subscribes to that publisher is
|
|
||||||
* then repeatedly scheduled according to the annotation configuration.
|
|
||||||
* <p>Note that for fixed delay configuration, the subscription is turned into a blocking
|
|
||||||
* call instead. Types for which a {@code ReactiveAdapter} is registered but which cannot
|
|
||||||
* be deferred (i.e. not a {@code Publisher}) are not supported.
|
|
||||||
* @param scheduled the {@code @Scheduled} annotation
|
|
||||||
* @param method the method that the annotation has been declared on, which
|
|
||||||
* must either return a Publisher-adaptable type or be a Kotlin suspending function
|
|
||||||
* @param bean the target bean instance
|
|
||||||
* @see ScheduledAnnotationReactiveSupport
|
|
||||||
*/
|
|
||||||
protected void processScheduledAsync(Scheduled scheduled, Method method, Object bean) {
|
|
||||||
Runnable task;
|
|
||||||
try {
|
|
||||||
task = ScheduledAnnotationReactiveSupport.createSubscriptionRunnable(method, bean, scheduled,
|
|
||||||
this.registrar::getObservationRegistry,
|
|
||||||
this.reactiveSubscriptions.computeIfAbsent(bean, k -> new CopyOnWriteArrayList<>()));
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException ex) {
|
|
||||||
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" + method.getName() + "': " + ex.getMessage());
|
|
||||||
}
|
|
||||||
processScheduledTask(scheduled, task, method, bean);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link Runnable} for the given bean instance,
|
* Create a {@link Runnable} for the given bean instance,
|
||||||
* calling the specified scheduled method.
|
* calling the specified scheduled method.
|
||||||
|
|
|
@ -107,18 +107,18 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
|
||||||
|
|
||||||
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reactive Streams API present on the classpath?
|
||||||
|
*/
|
||||||
|
private static final boolean reactiveStreamsPresent = ClassUtils.isPresent(
|
||||||
|
"org.reactivestreams.Publisher", TransactionAspectSupport.class.getClassLoader());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vavr library present on the classpath?
|
* Vavr library present on the classpath?
|
||||||
*/
|
*/
|
||||||
private static final boolean vavrPresent = ClassUtils.isPresent(
|
private static final boolean vavrPresent = ClassUtils.isPresent(
|
||||||
"io.vavr.control.Try", TransactionAspectSupport.class.getClassLoader());
|
"io.vavr.control.Try", TransactionAspectSupport.class.getClassLoader());
|
||||||
|
|
||||||
/**
|
|
||||||
* Reactive Streams API present on the classpath?
|
|
||||||
*/
|
|
||||||
private static final boolean reactiveStreamsPresent =
|
|
||||||
ClassUtils.isPresent("org.reactivestreams.Publisher", TransactionAspectSupport.class.getClassLoader());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holder to support the {@code currentTransactionStatus()} method,
|
* Holder to support the {@code currentTransactionStatus()} method,
|
||||||
* and to support communication between different cooperating advices
|
* and to support communication between different cooperating advices
|
||||||
|
|
Loading…
Reference in New Issue