diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index 56f7c2d7a1..a853ecb1ab 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -115,6 +115,8 @@ public class ScheduledAnnotationBeanPostProcessor protected final Log logger = LogFactory.getLog(getClass()); + private final ScheduledTaskRegistrar registrar; + @Nullable private Object scheduler; @@ -130,13 +132,30 @@ public class ScheduledAnnotationBeanPostProcessor @Nullable private ApplicationContext applicationContext; - private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar(); - private final Set> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64)); private final Map> scheduledTasks = new IdentityHashMap<>(16); + /** + * Create a default {@code ScheduledAnnotationBeanPostProcessor}. + */ + public ScheduledAnnotationBeanPostProcessor() { + this.registrar = new ScheduledTaskRegistrar(); + } + + /** + * Create a {@code ScheduledAnnotationBeanPostProcessor} delegating to the + * specified {@link ScheduledTaskRegistrar}. + * @param registrar the ScheduledTaskRegistrar to register @Scheduled tasks on + * @since 5.1 + */ + public ScheduledAnnotationBeanPostProcessor(ScheduledTaskRegistrar registrar) { + Assert.notNull(registrar, "ScheduledTaskRegistrar is required"); + this.registrar = registrar; + } + + @Override public int getOrder() { return LOWEST_PRECEDENCE; @@ -340,13 +359,16 @@ public class ScheduledAnnotationBeanPostProcessor return bean; } + /** + * Process the given {@code @Scheduled} method declaration on the given bean. + * @param scheduled the @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 processScheduled(Scheduled scheduled, Method method, Object bean) { try { - Assert.isTrue(method.getParameterCount() == 0, - "Only no-arg methods may be annotated with @Scheduled"); - - Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass()); - Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod); + Runnable runnable = createRunnable(bean, method); boolean processedSchedule = false; String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required"; @@ -470,6 +492,23 @@ public class ScheduledAnnotationBeanPostProcessor } } + /** + * Create a {@link Runnable} for the given bean instance, + * calling the specified scheduled method. + *

The default implementation creates a {@link ScheduledMethodRunnable}. + * @param target the target bean instance + * @param method the scheduled method to call + * @since 5.1 + * @see ScheduledMethodRunnable#ScheduledMethodRunnable(Object, Method) + */ + protected Runnable createRunnable(Object target, Method method) { + Assert.isTrue(method.getParameterCount() == 0, + "Only no-arg methods may be annotated with @Scheduled"); + + Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass()); + return new ScheduledMethodRunnable(target, invocableMethod); + } + private static long parseDelayAsLong(String value) throws RuntimeException { if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) { return Duration.parse(value).toMillis();