Skip to content

Commit 18c6ace

Browse files
committed
Consistent guard for Reactive Streams presence
Closes gh-30707
1 parent 24ddcee commit 18c6ace

File tree

2 files changed

+65
-56
lines changed

2 files changed

+65
-56
lines changed

spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.springframework.scheduling.support.CronTrigger;
7575
import org.springframework.scheduling.support.ScheduledMethodRunnable;
7676
import org.springframework.util.Assert;
77+
import org.springframework.util.ClassUtils;
7778
import org.springframework.util.StringUtils;
7879
import org.springframework.util.StringValueResolver;
7980

@@ -122,6 +123,12 @@ public class ScheduledAnnotationBeanPostProcessor
122123
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
123124

124125

126+
/**
127+
* Reactive Streams API present on the classpath?
128+
*/
129+
private static final boolean reactiveStreamsPresent = ClassUtils.isPresent(
130+
"org.reactivestreams.Publisher", ScheduledAnnotationBeanPostProcessor.class.getClassLoader());
131+
125132
protected final Log logger = LogFactory.getLog(getClass());
126133

127134
private final ScheduledTaskRegistrar registrar;
@@ -402,13 +409,63 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
402409
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
403410
// Is the method a Kotlin suspending function? Throws if true and the reactor bridge isn't on the classpath.
404411
// Does the method return a reactive type? Throws if true and it isn't a deferred Publisher type.
405-
if (ScheduledAnnotationReactiveSupport.isReactive(method)) {
412+
if (reactiveStreamsPresent && ScheduledAnnotationReactiveSupport.isReactive(method)) {
406413
processScheduledAsync(scheduled, method, bean);
407414
return;
408415
}
409416
processScheduledSync(scheduled, method, bean);
410417
}
411418

419+
/**
420+
* Process the given {@code @Scheduled} method declaration on the given bean,
421+
* as a synchronous method. The method must accept no arguments. Its return value
422+
* is ignored (if any), and the scheduled invocations of the method take place
423+
* using the underlying {@link TaskScheduler} infrastructure.
424+
* @param scheduled the {@code @Scheduled} annotation
425+
* @param method the method that the annotation has been declared on
426+
* @param bean the target bean instance
427+
* @see #createRunnable(Object, Method)
428+
*/
429+
private void processScheduledSync(Scheduled scheduled, Method method, Object bean) {
430+
Runnable task;
431+
try {
432+
task = createRunnable(bean, method);
433+
}
434+
catch (IllegalArgumentException ex) {
435+
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
436+
method.getName() + "': " + ex.getMessage());
437+
}
438+
processScheduledTask(scheduled, task, method, bean);
439+
}
440+
441+
/**
442+
* Process the given {@code @Scheduled} bean method declaration which returns
443+
* a {@code Publisher}, or the given Kotlin suspending function converted to a
444+
* {@code Publisher}. A {@code Runnable} which subscribes to that publisher is
445+
* then repeatedly scheduled according to the annotation configuration.
446+
* <p>Note that for fixed delay configuration, the subscription is turned into a blocking
447+
* call instead. Types for which a {@code ReactiveAdapter} is registered but which cannot
448+
* be deferred (i.e. not a {@code Publisher}) are not supported.
449+
* @param scheduled the {@code @Scheduled} annotation
450+
* @param method the method that the annotation has been declared on, which
451+
* must either return a Publisher-adaptable type or be a Kotlin suspending function
452+
* @param bean the target bean instance
453+
* @see ScheduledAnnotationReactiveSupport
454+
*/
455+
private void processScheduledAsync(Scheduled scheduled, Method method, Object bean) {
456+
Runnable task;
457+
try {
458+
task = ScheduledAnnotationReactiveSupport.createSubscriptionRunnable(method, bean, scheduled,
459+
this.registrar::getObservationRegistry,
460+
this.reactiveSubscriptions.computeIfAbsent(bean, k -> new CopyOnWriteArrayList<>()));
461+
}
462+
catch (IllegalArgumentException ex) {
463+
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
464+
method.getName() + "': " + ex.getMessage());
465+
}
466+
processScheduledTask(scheduled, task, method, bean);
467+
}
468+
412469
/**
413470
* Parse the {@code Scheduled} annotation and schedule the provided {@code Runnable}
414471
* accordingly. The Runnable can represent either a synchronous method invocation
@@ -419,7 +476,7 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
419476
* @param method the method that the annotation has been declared on
420477
* @param bean the target bean instance
421478
*/
422-
protected void processScheduledTask(Scheduled scheduled, Runnable runnable, Method method, Object bean) {
479+
private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method method, Object bean) {
423480
try {
424481
boolean processedSchedule = false;
425482
String errorMessage =
@@ -543,54 +600,6 @@ protected void processScheduledTask(Scheduled scheduled, Runnable runnable, Meth
543600
}
544601
}
545602

546-
/**
547-
* Process the given {@code @Scheduled} method declaration on the given bean,
548-
* as a synchronous method. The method must accept no arguments. Its return value
549-
* is ignored (if any), and the scheduled invocations of the method take place
550-
* using the underlying {@link TaskScheduler} infrastructure.
551-
* @param scheduled the {@code @Scheduled} annotation
552-
* @param method the method that the annotation has been declared on
553-
* @param bean the target bean instance
554-
* @see #createRunnable(Object, Method)
555-
*/
556-
protected void processScheduledSync(Scheduled scheduled, Method method, Object bean) {
557-
Runnable task;
558-
try {
559-
task = createRunnable(bean, method);
560-
}
561-
catch (IllegalArgumentException ex) {
562-
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" + method.getName() + "': " + ex.getMessage());
563-
}
564-
processScheduledTask(scheduled, task, method, bean);
565-
}
566-
567-
/**
568-
* Process the given {@code @Scheduled} bean method declaration which returns
569-
* a {@code Publisher}, or the given Kotlin suspending function converted to a
570-
* {@code Publisher}. A {@code Runnable} which subscribes to that publisher is
571-
* then repeatedly scheduled according to the annotation configuration.
572-
* <p>Note that for fixed delay configuration, the subscription is turned into a blocking
573-
* call instead. Types for which a {@code ReactiveAdapter} is registered but which cannot
574-
* be deferred (i.e. not a {@code Publisher}) are not supported.
575-
* @param scheduled the {@code @Scheduled} annotation
576-
* @param method the method that the annotation has been declared on, which
577-
* must either return a Publisher-adaptable type or be a Kotlin suspending function
578-
* @param bean the target bean instance
579-
* @see ScheduledAnnotationReactiveSupport
580-
*/
581-
protected void processScheduledAsync(Scheduled scheduled, Method method, Object bean) {
582-
Runnable task;
583-
try {
584-
task = ScheduledAnnotationReactiveSupport.createSubscriptionRunnable(method, bean, scheduled,
585-
this.registrar::getObservationRegistry,
586-
this.reactiveSubscriptions.computeIfAbsent(bean, k -> new CopyOnWriteArrayList<>()));
587-
}
588-
catch (IllegalArgumentException ex) {
589-
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" + method.getName() + "': " + ex.getMessage());
590-
}
591-
processScheduledTask(scheduled, task, method, bean);
592-
}
593-
594603
/**
595604
* Create a {@link Runnable} for the given bean instance,
596605
* calling the specified scheduled method.

spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,16 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
108108
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
109109

110110
/**
111-
* Vavr library present on the classpath?
111+
* Reactive Streams API present on the classpath?
112112
*/
113-
private static final boolean vavrPresent = ClassUtils.isPresent(
114-
"io.vavr.control.Try", TransactionAspectSupport.class.getClassLoader());
113+
private static final boolean reactiveStreamsPresent = ClassUtils.isPresent(
114+
"org.reactivestreams.Publisher", TransactionAspectSupport.class.getClassLoader());
115115

116116
/**
117-
* Reactive Streams API present on the classpath?
117+
* Vavr library present on the classpath?
118118
*/
119-
private static final boolean reactiveStreamsPresent =
120-
ClassUtils.isPresent("org.reactivestreams.Publisher", TransactionAspectSupport.class.getClassLoader());
119+
private static final boolean vavrPresent = ClassUtils.isPresent(
120+
"io.vavr.control.Try", TransactionAspectSupport.class.getClassLoader());
121121

122122
/**
123123
* Holder to support the {@code currentTransactionStatus()} method,

0 commit comments

Comments
 (0)