1. 使用方式
@Component
public class PrintNewsJob {
@Scheduled(cron = "*0/3 * * * * ?")
public void myTasks() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + " 执行定时任务 打印MMMMMM " + LocalDateTime.now());
}
}
在启动类上加上@EnableScheduling
即可完成定时任务。
2. 可能会出现的问题
注意点:默认情况下,定时器的线程池只有有一个线程。所以出现多个定时任务,并且耗时比较久,会出现问题。
这里说的是单机状况下,没有说集群。集群的话,要是还想用Spring Task,可以使用ShedLock。
原因:
先看看配置类 spring-boot-autoconfigure包下spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskSchedulingProperties.class)
@AutoConfigureAfter(TaskExecutionAutoConfiguration.class)
public class TaskSchedulingAutoConfiguration {
@Bean
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
// 默认情况下:如果没有配置这些bean,那么就会创建一个poolSize=1 的ThreadPoolTaskScheduler
@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
return builder.build();
}
@Bean
@ConditionalOnMissingBean
public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
builder = builder.poolSize(properties.getPool().getSize());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskSchedulerCustomizers);
return builder;
}
}
org.springframework.boot.task.TaskSchedulerBuilder#build
public ThreadPoolTaskScheduler build() {
return this.configure(new ThreadPoolTaskScheduler());
}
ThreadPoolTaskScheduler
默认情况下,poolSize=1
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {
private volatile int poolSize = 1;
private volatile boolean removeOnCancelPolicy = false;
@Nullable
private volatile ErrorHandler errorHandler;
@Nullable
private ScheduledExecutorService scheduledExecutor;
3. 解决方式
方法也很简单,实现 SchedulingConfigurer
,设置一个线程池即可
@Configuration
public class TaskConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 这里只是演示代码,具体的线程池需要根据项目实际情况设置
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(2));
}
}
看看什么是否调用这个SchedulingConfigurer
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
// Running in an ApplicationContext -> register tasks this late...
// giving other ContextRefreshedEvent listeners a chance to perform
// their work at the same time (e.g. Spring Batch's job registration).
finishRegistration();
}
}
private void finishRegistration() {
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
if (this.beanFactory instanceof ListableBeanFactory) {
Map<String, SchedulingConfigurer> beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(configurers);
for (SchedulingConfigurer configurer : configurers) {
// 这里设置SchedulingConfigurer
configurer.configureTasks(this.registrar);
}
}
// 默认情况下,走这里
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) {
logger.trace("Could not find unique TaskScheduler bean", ex);
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
···
}
}
catch (NoSuchBeanDefinitionException ex) {
···
}
}
this.registrar.afterPropertiesSet();
org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#resolveSchedulerBean
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) {
((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(
DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);
}
return scheduler;
}
else if (beanFactory instanceof AutowireCapableBeanFactory) {
// 这里获取的TaskScheduler bean 就是 TaskSchedulingAutoConfiguration配置中创建的默认ThreadPoolTaskScheduler
NamedBeanHolder<T> holder = ((AutowireCapableBeanFactory) beanFactory).resolveNamedBean(schedulerType);
if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory) {
((ConfigurableBeanFactory) beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);
}
return holder.getBeanInstance();
}
else {
return beanFactory.getBean(schedulerType);
}
}
4. 动态配置corn
这里简单配置一个bean来替代数据库存储corn
@Data
@Component
public class ConfigCorn {
private String corn = "0/3 * * * * ?";
}
然后在配置类中设置
@Slf4j
@Configuration
public class TaskConfig implements SchedulingConfigurer {
@Autowired
private PrintNewsJob printNewsJob;
@Autowired
private ConfigCorn configCorn;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
log.info("当前设置SchedulingConfigurer");
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(2));
// 设置TriggerTask即可
taskRegistrar.addTriggerTask(
() -> printNewsJob.myTasks(),
triggerContext -> {
log.info("当前设置的corn表达式{}", configCorn.getCorn());
String corn = configCorn.getCorn();
return new CronTrigger(corn).nextExecutionTime(triggerContext);
}
);
}
}
在controller中代码
@Autowired
private ConfigCorn configCorn;
@PostMapping("changeCorn")
public Boolean changeCorn(String corn) {
configCorn.setCorn(corn);
return true;
}