@Schedule注解源码

181 阅读2分钟

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;
    }