Support for determining a target scheduler for a specific task

Introduces "scheduler" attribute on @Scheduled annotation.
TaskSchedulerRouter delegates to qualified/default scheduler.
ScheduledMethodRunnable exposes qualifier through SchedulingAwareRunnable.

Closes gh-20818
This commit is contained in:
Juergen Hoeller 2023-07-08 15:37:04 +02:00
parent f0fe58f0ec
commit a8614531ab
6 changed files with 460 additions and 109 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 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.
@ -16,6 +16,8 @@
package org.springframework.scheduling;
import org.springframework.lang.Nullable;
/**
* Extension of the {@link Runnable} interface, adding special callbacks
* for long-running operations.
@ -38,7 +40,27 @@ public interface SchedulingAwareRunnable extends Runnable {
* pool (if any) but rather be considered as long-running background thread.
* <p>This should be considered a hint. Of course TaskExecutor implementations
* are free to ignore this flag and the SchedulingAwareRunnable interface overall.
* <p>The default implementation returns {@code false}, as of 6.1.
*/
boolean isLongLived();
default boolean isLongLived() {
return false;
}
/**
* Return a qualifier associated with this Runnable.
* <p>The default implementation returns {@code null}.
* <p>May be used for custom purposes depending on the scheduler implementation.
* {@link org.springframework.scheduling.config.TaskSchedulerRouter} introspects
* this qualifier in order to determine the target scheduler to be used
* for a given Runnable, matching the qualifier value (or the bean name)
* of a specific {@link org.springframework.scheduling.TaskScheduler} or
* {@link java.util.concurrent.ScheduledExecutorService} bean definition.
* @since 6.1
* @see org.springframework.scheduling.annotation.Scheduled#scheduler()
*/
@Nullable
default String getQualifier() {
return null;
}
}

View File

@ -203,4 +203,16 @@ public @interface Scheduled {
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* A qualifier for determining a scheduler to run this scheduled method on.
* <p>Defaults to an empty String, suggesting the default scheduler.
* <p>May be used to determine the target scheduler to be used,
* matching the qualifier value (or the bean name) of a specific
* {@link org.springframework.scheduling.TaskScheduler} or
* {@link java.util.concurrent.ScheduledExecutorService} bean definition.
* @since 6.1
* @see org.springframework.scheduling.SchedulingAwareRunnable#getQualifier()
*/
String scheduler() default "";
}

View File

@ -43,13 +43,8 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.config.NamedBeanHolder;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
@ -71,6 +66,7 @@ import org.springframework.scheduling.config.FixedRateTask;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskHolder;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.config.TaskSchedulerRouter;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.util.Assert;
@ -120,7 +116,7 @@ public class ScheduledAnnotationBeanPostProcessor
* in case of multiple scheduler beans found in the context.
* @since 4.2
*/
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = TaskSchedulerRouter.DEFAULT_TASK_SCHEDULER_BEAN_NAME;
/**
@ -254,6 +250,12 @@ public class ScheduledAnnotationBeanPostProcessor
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
else {
TaskSchedulerRouter router = new TaskSchedulerRouter();
router.setBeanName(this.beanName);
router.setBeanFactory(this.beanFactory);
this.registrar.setTaskScheduler(router);
}
if (this.beanFactory instanceof ListableBeanFactory lbf) {
Map<String, SchedulingConfigurer> beans = lbf.getBeansOfType(SchedulingConfigurer.class);
@ -264,91 +266,9 @@ public class ScheduledAnnotationBeanPostProcessor
}
}
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
try {
// Search for TaskScheduler bean...
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
}
catch (NoUniqueBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +
ex.getMessage());
}
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskScheduler bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +
ex.getMessage());
}
// Search for ScheduledExecutorService bean next...
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +
ex2.getMessage());
}
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
if (logger.isInfoEnabled()) {
logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex2.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +
ex2.getMessage());
}
// Giving up -> falling back to default scheduler within the registrar...
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
this.registrar.afterPropertiesSet();
}
private <T> T resolveSchedulerBean(BeanFactory beanFactory, Class<T> schedulerType, boolean byName) {
if (byName) {
T scheduler = beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);
if (this.beanName != null && this.beanFactory instanceof ConfigurableBeanFactory cbf) {
cbf.registerDependentBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);
}
return scheduler;
}
else if (beanFactory instanceof AutowireCapableBeanFactory acbf) {
NamedBeanHolder<T> holder = acbf.resolveNamedBean(schedulerType);
if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory cbf) {
cbf.registerDependentBean(holder.getBeanName(), this.beanName);
}
return holder.getBeanInstance();
}
else {
return beanFactory.getBean(schedulerType);
}
}
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
@ -424,12 +344,11 @@ public class ScheduledAnnotationBeanPostProcessor
* @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);
task = createRunnable(bean, method, scheduled.scheduler());
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
@ -606,13 +525,31 @@ public class ScheduledAnnotationBeanPostProcessor
* <p>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)
* @since 6.1
*/
protected Runnable createRunnable(Object target, Method method) {
@SuppressWarnings("deprecation")
protected Runnable createRunnable(Object target, Method method, @Nullable String qualifier) {
Runnable runnable = createRunnable(target, method);
if (runnable != null) {
return runnable;
}
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, this.registrar::getObservationRegistry);
return new ScheduledMethodRunnable(target, invocableMethod, qualifier, this.registrar::getObservationRegistry);
}
/**
* Create a {@link Runnable} for the given bean instance,
* calling the specified scheduled method.
* @param target the target bean instance
* @param method the scheduled method to call
* @since 5.1
* @deprecated in favor of {@link #createRunnable(Object, Method, String)}
*/
@Deprecated(since = "6.1")
@Nullable
protected Runnable createRunnable(Object target, Method method) {
return null;
}
private static Duration toDuration(long value, TimeUnit timeUnit) {

View File

@ -0,0 +1,264 @@
/*
* Copyright 2002-2023 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.scheduling.config;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.beans.factory.config.NamedBeanHolder;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.SchedulingAwareRunnable;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
import org.springframework.util.function.SingletonSupplier;
/**
* A routing implementation of the {@link TaskScheduler} interface,
* delegating to a target scheduler based on an identified qualifier
* or using a default scheduler otherwise.
*
* @author Juergen Hoeller
* @since 6.1
* @see SchedulingAwareRunnable#getQualifier()
*/
public class TaskSchedulerRouter implements TaskScheduler, BeanNameAware, BeanFactoryAware, DisposableBean {
/**
* The default name of the {@link TaskScheduler} bean to pick up: {@value}.
* <p>Note that the initial lookup happens by type; this is just the fallback
* in case of multiple scheduler beans found in the context.
*/
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
protected static final Log logger = LogFactory.getLog(TaskSchedulerRouter.class);
@Nullable
private String beanName;
@Nullable
private BeanFactory beanFactory;
@Nullable
private StringValueResolver embeddedValueResolver;
private final Supplier<TaskScheduler> defaultScheduler = SingletonSupplier.of(this::determineDefaultScheduler);
@Nullable
private volatile ScheduledExecutorService localExecutor;
/**
* The bean name for this router, or the bean name of the containing
* bean if the router instance is internally held.
*/
@Override
public void setBeanName(@Nullable String name) {
this.beanName = name;
}
/**
* The bean factory for scheduler lookups.
*/
@Override
public void setBeanFactory(@Nullable BeanFactory beanFactory) {
this.beanFactory = beanFactory;
if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) {
this.embeddedValueResolver = new EmbeddedValueResolver(configurableBeanFactory);
}
}
@Override
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
return determineTargetScheduler(task).schedule(task, trigger);
}
@Override
public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
return determineTargetScheduler(task).schedule(task, startTime);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) {
return determineTargetScheduler(task).scheduleAtFixedRate(task, startTime, period);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
return determineTargetScheduler(task).scheduleAtFixedRate(task, period);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay) {
return determineTargetScheduler(task).scheduleWithFixedDelay(task, startTime, delay);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
return determineTargetScheduler(task).scheduleWithFixedDelay(task, delay);
}
protected TaskScheduler determineTargetScheduler(Runnable task) {
String qualifier = determineQualifier(task);
if (this.embeddedValueResolver != null && StringUtils.hasLength(qualifier)) {
qualifier = this.embeddedValueResolver.resolveStringValue(qualifier);
}
if (StringUtils.hasLength(qualifier)) {
return determineQualifiedScheduler(qualifier);
}
else {
return this.defaultScheduler.get();
}
}
@Nullable
protected String determineQualifier(Runnable task) {
return (task instanceof SchedulingAwareRunnable sar ? sar.getQualifier() : null);
}
protected TaskScheduler determineQualifiedScheduler(String qualifier) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find qualified scheduler");
try {
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, TaskScheduler.class, qualifier);
}
catch (NoSuchBeanDefinitionException | BeanNotOfRequiredTypeException ex) {
return new ConcurrentTaskScheduler(BeanFactoryAnnotationUtils.qualifiedBeanOfType(
this.beanFactory, ScheduledExecutorService.class, qualifier));
}
}
protected TaskScheduler determineDefaultScheduler() {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find default scheduler");
try {
// Search for TaskScheduler bean...
return resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false);
}
catch (NoUniqueBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +
ex.getMessage());
}
try {
return resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true);
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskScheduler bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +
ex.getMessage());
}
// Search for ScheduledExecutorService bean next...
try {
return new ConcurrentTaskScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +
ex2.getMessage());
}
try {
return new ConcurrentTaskScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
if (logger.isInfoEnabled()) {
logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex2.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +
ex2.getMessage());
}
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor();
this.localExecutor = localExecutor;
return new ConcurrentTaskScheduler(localExecutor);
}
private <T> T resolveSchedulerBean(BeanFactory beanFactory, Class<T> schedulerType, boolean byName) {
if (byName) {
T scheduler = beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);
if (this.beanName != null && this.beanFactory instanceof ConfigurableBeanFactory cbf) {
cbf.registerDependentBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);
}
return scheduler;
}
else if (beanFactory instanceof AutowireCapableBeanFactory acbf) {
NamedBeanHolder<T> holder = acbf.resolveNamedBean(schedulerType);
if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory cbf) {
cbf.registerDependentBean(holder.getBeanName(), this.beanName);
}
return holder.getBeanInstance();
}
else {
return beanFactory.getBean(schedulerType);
}
}
/**
* Destroy the local default executor, if any.
*/
@Override
public void destroy() {
ScheduledExecutorService localExecutor = this.localExecutor;
if (localExecutor != null) {
localExecutor.shutdownNow();
}
}
}

View File

@ -24,6 +24,8 @@ import java.util.function.Supplier;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.SchedulingAwareRunnable;
import org.springframework.util.ReflectionUtils;
/**
@ -36,7 +38,7 @@ import org.springframework.util.ReflectionUtils;
* @since 3.0.6
* @see org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor
*/
public class ScheduledMethodRunnable implements Runnable {
public class ScheduledMethodRunnable implements SchedulingAwareRunnable {
private static final ScheduledTaskObservationConvention DEFAULT_CONVENTION =
new DefaultScheduledTaskObservationConvention();
@ -45,6 +47,9 @@ public class ScheduledMethodRunnable implements Runnable {
private final Method method;
@Nullable
private final String qualifier;
private final Supplier<ObservationRegistry> observationRegistrySupplier;
@ -53,12 +58,17 @@ public class ScheduledMethodRunnable implements Runnable {
* calling the specified method.
* @param target the target instance to call the method on
* @param method the target method to call
* @param qualifier a qualifier associated with this Runnable,
* e.g. for determining a scheduler to run this scheduled method on
* @param observationRegistrySupplier a supplier for the observation registry to use
* @since 6.1
*/
public ScheduledMethodRunnable(Object target, Method method, Supplier<ObservationRegistry> observationRegistrySupplier) {
public ScheduledMethodRunnable(Object target, Method method, @Nullable String qualifier,
Supplier<ObservationRegistry> observationRegistrySupplier) {
this.target = target;
this.method = method;
this.qualifier = qualifier;
this.observationRegistrySupplier = observationRegistrySupplier;
}
@ -69,7 +79,7 @@ public class ScheduledMethodRunnable implements Runnable {
* @param method the target method to call
*/
public ScheduledMethodRunnable(Object target, Method method) {
this(target, method, () -> ObservationRegistry.NOOP);
this(target, method, null, () -> ObservationRegistry.NOOP);
}
/**
@ -98,6 +108,12 @@ public class ScheduledMethodRunnable implements Runnable {
return this.method;
}
@Override
@Nullable
public String getQualifier() {
return this.qualifier;
}
@Override
public void run() {

View File

@ -20,14 +20,17 @@ import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.testfixture.EnabledForTestGroups;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@ -65,7 +68,7 @@ public class EnableSchedulingTests {
ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfig.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(2);
Thread.sleep(100);
Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
}
@ -75,7 +78,7 @@ public class EnableSchedulingTests {
ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfigSubclass.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(2);
Thread.sleep(100);
Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
}
@ -85,15 +88,15 @@ public class EnableSchedulingTests {
ctx = new AnnotationConfigApplicationContext(ExplicitSchedulerConfig.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(1);
Thread.sleep(100);
Thread.sleep(110);
ctx.stop();
int count1 = ctx.getBean(AtomicInteger.class).get();
assertThat(count1).isGreaterThanOrEqualTo(10);
Thread.sleep(100);
Thread.sleep(110);
int count2 = ctx.getBean(AtomicInteger.class).get();
assertThat(count2).isEqualTo(count1);
ctx.start();
Thread.sleep(100);
Thread.sleep(110);
int count3 = ctx.getBean(AtomicInteger.class).get();
assertThat(count3).isGreaterThanOrEqualTo(20);
@ -114,11 +117,33 @@ public class EnableSchedulingTests {
ctx = new AnnotationConfigApplicationContext(ExplicitScheduledTaskRegistrarConfig.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(1);
Thread.sleep(100);
Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
assertThat(ctx.getBean(ExplicitScheduledTaskRegistrarConfig.class).threadName).startsWith("explicitScheduler1");
}
@Test
@EnabledForTestGroups(LONG_RUNNING)
public void withQualifiedScheduler() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(QualifiedExplicitSchedulerConfig.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(1);
Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
assertThat(ctx.getBean(QualifiedExplicitSchedulerConfig.class).threadName).startsWith("explicitScheduler1");
}
@Test
@EnabledForTestGroups(LONG_RUNNING)
public void withQualifiedSchedulerAndPlaceholder() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(QualifiedExplicitSchedulerConfigWithPlaceholder.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(1);
Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
assertThat(ctx.getBean(QualifiedExplicitSchedulerConfigWithPlaceholder.class).threadName).startsWith("explicitScheduler1");
}
@Test
public void withAmbiguousTaskSchedulers_butNoActualTasks() {
ctx = new AnnotationConfigApplicationContext(SchedulingEnabled_withAmbiguousTaskSchedulers_butNoActualTasks.class);
@ -136,7 +161,7 @@ public class EnableSchedulingTests {
ctx = new AnnotationConfigApplicationContext(
SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedByScheduledTaskRegistrar.class);
Thread.sleep(100);
Thread.sleep(110);
assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread).startsWith("explicitScheduler2-");
}
@ -146,7 +171,7 @@ public class EnableSchedulingTests {
ctx = new AnnotationConfigApplicationContext(
SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNameAttribute.class);
Thread.sleep(100);
Thread.sleep(110);
assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread).startsWith("explicitScheduler2-");
}
@ -155,7 +180,7 @@ public class EnableSchedulingTests {
public void withTaskAddedVia_configureTasks() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(SchedulingEnabled_withTaskAddedVia_configureTasks.class);
Thread.sleep(100);
Thread.sleep(110);
assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread).startsWith("taskScheduler-");
}
@ -164,7 +189,7 @@ public class EnableSchedulingTests {
public void withTriggerTask() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(TriggerTaskConfig.class);
Thread.sleep(100);
Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThan(1);
}
@ -296,6 +321,81 @@ public class EnableSchedulingTests {
}
@Configuration
@EnableScheduling
static class QualifiedExplicitSchedulerConfig {
String threadName;
@Bean @Qualifier("myScheduler")
public TaskScheduler taskScheduler1() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("explicitScheduler1");
return scheduler;
}
@Bean
public TaskScheduler taskScheduler2() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("explicitScheduler2");
return scheduler;
}
@Bean
public AtomicInteger counter() {
return new AtomicInteger();
}
@Scheduled(fixedRate = 10, scheduler = "myScheduler")
public void task() {
threadName = Thread.currentThread().getName();
counter().incrementAndGet();
}
}
@Configuration
@EnableScheduling
static class QualifiedExplicitSchedulerConfigWithPlaceholder {
String threadName;
@Bean @Qualifier("myScheduler")
public TaskScheduler taskScheduler1() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("explicitScheduler1");
return scheduler;
}
@Bean
public TaskScheduler taskScheduler2() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("explicitScheduler2");
return scheduler;
}
@Bean
public AtomicInteger counter() {
return new AtomicInteger();
}
@Scheduled(fixedRate = 10, scheduler = "${scheduler}")
public void task() {
threadName = Thread.currentThread().getName();
counter().incrementAndGet();
}
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
Properties props = new Properties();
props.setProperty("scheduler", "myScheduler");
pspc.setProperties(props);
return pspc;
}
}
@Configuration
@EnableScheduling
static class SchedulingEnabled_withAmbiguousTaskSchedulers_butNoActualTasks {