@@ -143,7 +143,6 @@ public class ScheduledAnnotationBeanPostProcessor
143
143
private final Set <Class <?>> nonAnnotatedClasses = Collections .newSetFromMap (new ConcurrentHashMap <>(64 ));
144
144
145
145
private final Map <Object , Set <ScheduledTask >> scheduledTasks = new IdentityHashMap <>(16 );
146
- private final Map <Object , Set <ScheduledAnnotationReactiveSupport .ReactiveTask >> scheduledReactiveTasks = new IdentityHashMap <>(16 );
147
146
148
147
149
148
/**
@@ -318,15 +317,6 @@ private void finishRegistration() {
318
317
}
319
318
320
319
this .registrar .afterPropertiesSet ();
321
- // Start the reactive tasks (we synchronize on the common scheduledTasks on purpose)
322
- synchronized (this .scheduledTasks ) {
323
- for (Set <ScheduledAnnotationReactiveSupport .ReactiveTask > reactiveTasks : this .scheduledReactiveTasks .values ()) {
324
- for (ScheduledAnnotationReactiveSupport .ReactiveTask reactiveTask : reactiveTasks ) {
325
- reactiveTask .subscribe ();
326
- }
327
- }
328
- }
329
-
330
320
}
331
321
332
322
private <T > T resolveSchedulerBean (BeanFactory beanFactory , Class <T > schedulerType , boolean byName ) {
@@ -397,37 +387,32 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
397
387
398
388
/**
399
389
* Process the given {@code @Scheduled} method declaration on the given bean,
400
- * attempting to distinguish {@link #processScheduledReactive (Scheduled, Method, Object) reactive}
401
- * method from {@link #processScheduledSync(Scheduled, Method, Object) synchronous} methods.
390
+ * attempting to distinguish {@link #processScheduledAsync (Scheduled, Method, Object) reactive}
391
+ * methods from {@link #processScheduledSync(Scheduled, Method, Object) synchronous} methods.
402
392
* @param scheduled the {@code @Scheduled} annotation
403
393
* @param method the method that the annotation has been declared on
404
394
* @param bean the target bean instance
405
395
* @see #processScheduledSync(Scheduled, Method, Object)
406
- * @see #processScheduledReactive (Scheduled, Method, Object)
396
+ * @see #processScheduledAsync (Scheduled, Method, Object)
407
397
*/
408
398
protected void processScheduled (Scheduled scheduled , Method method , Object bean ) {
409
399
// Is method a Kotlin suspending function? Throws if true but reactor bridge isn't on the classpath.
410
- // Is method returning a Publisher instance ? Throws if true but Reactor isn't on the classpath .
400
+ // Is method returning a reactive type ? Throws if true, but it isn't a deferred Publisher type .
411
401
if (ScheduledAnnotationReactiveSupport .isReactive (method )) {
412
- processScheduledReactive (scheduled , method , bean );
402
+ processScheduledAsync (scheduled , method , bean );
413
403
return ;
414
404
}
415
405
processScheduledSync (scheduled , method , bean );
416
406
}
417
407
418
408
/**
419
- * Process the given {@code @Scheduled} method declaration on the given bean,
420
- * as a synchronous method. The method MUST take no arguments. Its return value
421
- * is ignored (if any) and the scheduled invocations of the method take place
422
- * using the underlying {@link TaskScheduler} infrastructure.
423
- * @param scheduled the {@code @Scheduled} annotation
424
- * @param method the method that the annotation has been declared on
425
- * @param bean the target bean instance
426
- * @see #createRunnable(Object, Method)
409
+ * Parse the {@code Scheduled} annotation and schedule the provided {@code Runnable}
410
+ * accordingly. The Runnable can represent either a synchronous method invocation
411
+ * (see {@link #processScheduledSync(Scheduled, Method, Object)}) or an asynchronous
412
+ * one (see {@link #processScheduledAsync(Scheduled, Method, Object)}).
427
413
*/
428
- protected void processScheduledSync (Scheduled scheduled , Method method , Object bean ) {
414
+ protected void processScheduledTask (Scheduled scheduled , Runnable runnable , Method method , Object bean ) {
429
415
try {
430
- Runnable runnable = createRunnable (bean , method );
431
416
boolean processedSchedule = false ;
432
417
String errorMessage =
433
418
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required" ;
@@ -550,118 +535,51 @@ protected void processScheduledSync(Scheduled scheduled, Method method, Object b
550
535
}
551
536
}
552
537
538
+ /**
539
+ * Process the given {@code @Scheduled} method declaration on the given bean,
540
+ * as a synchronous method. The method MUST take no arguments. Its return value
541
+ * is ignored (if any) and the scheduled invocations of the method take place
542
+ * using the underlying {@link TaskScheduler} infrastructure.
543
+ * @param scheduled the {@code @Scheduled} annotation
544
+ * @param method the method that the annotation has been declared on
545
+ * @param bean the target bean instance
546
+ * @see #createRunnable(Object, Method)
547
+ */
548
+ protected void processScheduledSync (Scheduled scheduled , Method method , Object bean ) {
549
+ Runnable task ;
550
+ try {
551
+ task = createRunnable (bean , method );
552
+ }
553
+ catch (IllegalArgumentException ex ) {
554
+ throw new IllegalStateException ("Could not create recurring task for @Scheduled method '" + method .getName () + "': " + ex .getMessage ());
555
+ }
556
+ processScheduledTask (scheduled , task , method , bean );
557
+ }
558
+
553
559
/**
554
560
* Process the given {@code @Scheduled} bean method declaration which returns
555
561
* a {@code Publisher}, or the given Kotlin suspending function converted to a
556
- * Publisher. The publisher is then repeatedly subscribed to, according to the
557
- * fixedDelay/fixedRate configuration. Cron configuration isn't supported,nor
558
- * is non-Publisher return types (even if a {@code ReactiveAdapter} is registered).
562
+ * Publisher. A {@code Runnable} which subscribes to that publisher is then repeatedly
563
+ * scheduled according to the annotation configuration.
564
+ * <p>Note that for fixed delay configuration, the subscription is turned into a blocking
565
+ * call instead. Types for which a {@code ReactiveAdapter} is registered but which cannot
566
+ * be deferred (i.e. not a {@code Publisher}) are not supported.
559
567
* @param scheduled the {@code @Scheduled} annotation
560
568
* @param method the method that the annotation has been declared on, which
561
- * MUST either return a Publisher or be a Kotlin suspending function
569
+ * MUST either return a Publisher-adaptable type or be a Kotlin suspending function
562
570
* @param bean the target bean instance
563
571
* @see ScheduledAnnotationReactiveSupport
564
572
*/
565
- protected void processScheduledReactive (Scheduled scheduled , Method method , Object bean ) {
573
+ protected void processScheduledAsync (Scheduled scheduled , Method method , Object bean ) {
574
+ Runnable task ;
566
575
try {
567
- boolean processedSchedule = false ;
568
- String errorMessage =
569
- "Exactly one of the 'fixedDelay(String)' or 'fixedRate(String)' attributes is required" ;
570
-
571
- Set <ScheduledAnnotationReactiveSupport .ReactiveTask > reactiveTasks = new LinkedHashSet <>(4 );
572
-
573
- // Determine initial delay
574
- Duration initialDelay = toDuration (scheduled .initialDelay (), scheduled .timeUnit ());
575
- String initialDelayString = scheduled .initialDelayString ();
576
- if (StringUtils .hasText (initialDelayString )) {
577
- Assert .isTrue (initialDelay .isNegative (), "Specify 'initialDelay' or 'initialDelayString', not both" );
578
- if (this .embeddedValueResolver != null ) {
579
- initialDelayString = this .embeddedValueResolver .resolveStringValue (initialDelayString );
580
- }
581
- if (StringUtils .hasLength (initialDelayString )) {
582
- try {
583
- initialDelay = toDuration (initialDelayString , scheduled .timeUnit ());
584
- }
585
- catch (RuntimeException ex ) {
586
- throw new IllegalArgumentException (
587
- "Invalid initialDelayString value \" " + initialDelayString + "\" - cannot parse into long" );
588
- }
589
- }
590
- }
591
-
592
- // Reject cron expression
593
- Assert .state (!StringUtils .hasText (scheduled .cron ()), "'cron' not supported for reactive @Scheduled" );
594
-
595
- // At this point we don't need to differentiate between initial delay set or not anymore
596
- if (initialDelay .isNegative ()) {
597
- initialDelay = Duration .ZERO ;
598
- }
599
-
600
- // Check fixed delay
601
- Duration fixedDelay = toDuration (scheduled .fixedDelay (), scheduled .timeUnit ());
602
- if (!fixedDelay .isNegative ()) {
603
- processedSchedule = true ;
604
- reactiveTasks .add (new ScheduledAnnotationReactiveSupport .ReactiveTask (method , bean , initialDelay , fixedDelay , false ));
605
- }
606
-
607
- String fixedDelayString = scheduled .fixedDelayString ();
608
- if (StringUtils .hasText (fixedDelayString )) {
609
- if (this .embeddedValueResolver != null ) {
610
- fixedDelayString = this .embeddedValueResolver .resolveStringValue (fixedDelayString );
611
- }
612
- if (StringUtils .hasLength (fixedDelayString )) {
613
- Assert .isTrue (!processedSchedule , errorMessage );
614
- processedSchedule = true ;
615
- try {
616
- fixedDelay = toDuration (fixedDelayString , scheduled .timeUnit ());
617
- }
618
- catch (RuntimeException ex ) {
619
- throw new IllegalArgumentException (
620
- "Invalid fixedDelayString value \" " + fixedDelayString + "\" - cannot parse into long" );
621
- }
622
- reactiveTasks .add (new ScheduledAnnotationReactiveSupport .ReactiveTask (method , bean , initialDelay , fixedDelay , false ));
623
- }
624
- }
625
-
626
- // Check fixed rate
627
- Duration fixedRate = toDuration (scheduled .fixedRate (), scheduled .timeUnit ());
628
- if (!fixedRate .isNegative ()) {
629
- Assert .isTrue (!processedSchedule , errorMessage );
630
- processedSchedule = true ;
631
- reactiveTasks .add (new ScheduledAnnotationReactiveSupport .ReactiveTask (method , bean , initialDelay , fixedRate , true ));
632
- }
633
- String fixedRateString = scheduled .fixedRateString ();
634
- if (StringUtils .hasText (fixedRateString )) {
635
- if (this .embeddedValueResolver != null ) {
636
- fixedRateString = this .embeddedValueResolver .resolveStringValue (fixedRateString );
637
- }
638
- if (StringUtils .hasLength (fixedRateString )) {
639
- Assert .isTrue (!processedSchedule , errorMessage );
640
- processedSchedule = true ;
641
- try {
642
- fixedRate = toDuration (fixedRateString , scheduled .timeUnit ());
643
- }
644
- catch (RuntimeException ex ) {
645
- throw new IllegalArgumentException (
646
- "Invalid fixedRateString value \" " + fixedRateString + "\" - cannot parse into long" );
647
- }
648
- reactiveTasks .add (new ScheduledAnnotationReactiveSupport .ReactiveTask (method , bean , initialDelay , fixedRate , true ));
649
- }
650
- }
651
-
652
- // Check whether we had any attribute set
653
- Assert .isTrue (processedSchedule , errorMessage );
654
-
655
- // Finally register the scheduled tasks (we synchronize on scheduledTasks on purpose)
656
- synchronized (this .scheduledTasks ) {
657
- Set <ScheduledAnnotationReactiveSupport .ReactiveTask > subscriptionTasks = this .scheduledReactiveTasks .computeIfAbsent (bean , key -> new LinkedHashSet <>(4 ));
658
- subscriptionTasks .addAll (reactiveTasks );
659
- }
576
+ boolean isFixedDelaySpecialCase = scheduled .fixedDelay () > 0 || StringUtils .hasText (scheduled .fixedDelayString ());
577
+ task = ScheduledAnnotationReactiveSupport .createSubscriptionRunnable (method , bean , isFixedDelaySpecialCase );
660
578
}
661
579
catch (IllegalArgumentException ex ) {
662
- throw new IllegalStateException (
663
- "Encountered invalid reactive @Scheduled method '" + method .getName () + "': " + ex .getMessage (), ex );
580
+ throw new IllegalStateException ("Could not create recurring task for @Scheduled method '" + method .getName () + "': " + ex .getMessage ());
664
581
}
582
+ processScheduledTask (scheduled , task , method , bean );
665
583
}
666
584
667
585
/**
@@ -721,27 +639,20 @@ public Set<ScheduledTask> getScheduledTasks() {
721
639
@ Override
722
640
public void postProcessBeforeDestruction (Object bean , String beanName ) {
723
641
Set <ScheduledTask > tasks ;
724
- Set <ScheduledAnnotationReactiveSupport .ReactiveTask > reactiveTasks ;
725
642
synchronized (this .scheduledTasks ) {
726
643
tasks = this .scheduledTasks .remove (bean );
727
- reactiveTasks = this .scheduledReactiveTasks .remove (bean );
728
644
}
729
645
if (tasks != null ) {
730
646
for (ScheduledTask task : tasks ) {
731
647
task .cancel ();
732
648
}
733
649
}
734
- if (reactiveTasks != null ) {
735
- for (ScheduledAnnotationReactiveSupport .ReactiveTask task : reactiveTasks ) {
736
- task .cancel ();
737
- }
738
- }
739
650
}
740
651
741
652
@ Override
742
653
public boolean requiresDestruction (Object bean ) {
743
654
synchronized (this .scheduledTasks ) {
744
- return this .scheduledTasks .containsKey (bean ) || this . scheduledReactiveTasks . containsKey ( bean ) ;
655
+ return this .scheduledTasks .containsKey (bean );
745
656
}
746
657
}
747
658
@@ -755,13 +666,6 @@ public void destroy() {
755
666
}
756
667
}
757
668
this .scheduledTasks .clear ();
758
- Collection <Set <ScheduledAnnotationReactiveSupport .ReactiveTask >> allReactiveTasks = this .scheduledReactiveTasks .values ();
759
- for (Set <ScheduledAnnotationReactiveSupport .ReactiveTask > tasks : allReactiveTasks ) {
760
- for (ScheduledAnnotationReactiveSupport .ReactiveTask task : tasks ) {
761
- task .cancel ();
762
- }
763
- }
764
- this .scheduledReactiveTasks .clear ();
765
669
}
766
670
this .registrar .destroy ();
767
671
}
0 commit comments