定时任务是我们平常业务开发中常用的工具,凌晨统计数据,清理历史日志等等。spring非常友好的给我们提供了定时任务调度基础设施。
我们平常经常这样配置定时任务:
@EnableScheduling // 开启定时任务
@Component
public class Sch01 {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Scheduled(cron = "* * * * * ?")
public void sch001() {
logger.info("hello 1");
}
}
效果很容易达到:
2021-07-21 22:23:55.015 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:23:56.015 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:23:57.004 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:23:58.009 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:23:59.015 INFO 255 - [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
....
简单的一个cron表达式就能轻松搞定,但是背后的原理也是值得分析,并且做到定制话自己的任务调度模式。
@EnableScheduling作为定时调度的开关,很符合spring的编程风格
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
其最主要的目的就是import配置类SchedulingConfiguration
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
//简单注意下这个beanPostProcessor,其实现了很多接口,但是都相对熟悉
public class ScheduledAnnotationBeanPostProcessor
implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
/**
* Create a default {@code ScheduledAnnotationBeanPostProcessor}.
*/
public ScheduledAnnotationBeanPostProcessor() {
//设置任务注册中心,可以理解为一个容器,存放解析@Scheduled注解的方法解析后的信息
this.registrar = new ScheduledTaskRegistrar();
}
//....
}
可见主要配置了一个BeanPostProcessor,在每个bean创建生命周期中postProcessAfterInitialization会调被用。
public Object postProcessAfterInitialization(Object bean, String beanName) {
//忽略aop切面类,TaskScheduler及ScheduledExecutorService执行器类本身
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
//获取原始类(最终的)
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
//
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
//扫描类中是否带指定注解
Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
});
//不存在定时任务注解
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
}
} else {
// 存在定时任务注解,对每个方法进行解析
annotatedMethods.forEach((method, scheduledAnnotations) ->
//解析processScheduled
scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
跟踪processScheduled方法
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
//对方法进行封装为ScheduledMethodRunnable
Runnable runnable = createRunnable(bean, method);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// 解析起始延迟时间 @Scheduled(initialDelay = 5000L, cron = "* * * * * ?")
long initialDelay = scheduled.initialDelay();
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
if (this.embeddedValueResolver != null) {
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
if (StringUtils.hasLength(initialDelayString)) {
try {
initialDelay = parseDelayAsLong(initialDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
}
}
}
// 解析cron表达式模式 @Scheduled(initialDelay = 5000L, cron = "* * * * * ?")
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
//时区处理
String zone = scheduled.zone();
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
//解析cron表达式
if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
if (!Scheduled.CRON_DISABLED.equals(cron)) {
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();
}
//包装并添加任务到任务注册中心
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
}
// At this point we don't need to differentiate between initial delay set or not anymore
if (initialDelay < 0) {
initialDelay = 0;
}
// 解析延迟类型任务,同cron表达式一样,不在赘述
long fixedDelay = scheduled.fixedDelay();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
if (this.embeddedValueResolver != null) {
fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
}
if (StringUtils.hasLength(fixedDelayString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedDelay = parseDelayAsLong(fixedDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
}
// Check fixed rate
long fixedRate = scheduled.fixedRate();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
if (this.embeddedValueResolver != null) {
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
}
if (StringUtils.hasLength(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedRate = parseDelayAsLong(fixedRateString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
}
// Check whether we had any attribute set
Assert.isTrue(processedSchedule, errorMessage);
// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks);
}
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException(
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}
继续注册中心ScheduledTaskRegistrar的添加方法:
public ScheduledTask scheduleCronTask(CronTask task) {
//检查未解析的缓存
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
boolean newTask = false;
//包装
if (scheduledTask == null) {
scheduledTask = new ScheduledTask(task);
newTask = true;
}
//
if (this.taskScheduler != null) {
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
}
else {
//添加到待解析处理容器
addCronTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}
public void addCronTask(CronTask task) {
if (this.cronTasks == null) {
this.cronTasks = new ArrayList<>();
}
this.cronTasks.add(task);
}
我们可以近距离的看下注册中心有哪些容器
public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {
/**
* A special cron expression value that indicates a disabled trigger: {@value}.
* <p>This is primarily meant for use with {@link #addCronTask(Runnable, String)}
* when the value for the supplied {@code expression} is retrieved from an
* external source — for example, from a property in the
* {@link org.springframework.core.env.Environment Environment}.
* @since 5.2
* @see org.springframework.scheduling.annotation.Scheduled#CRON_DISABLED
*/
public static final String CRON_DISABLED = "-";
@Nullable
private TaskScheduler taskScheduler;
@Nullable
private ScheduledExecutorService localExecutor;
@Nullable
private List<TriggerTask> triggerTasks;
@Nullable
private List<CronTask> cronTasks;
@Nullable
private List<IntervalTask> fixedRateTasks;
@Nullable
private List<IntervalTask> fixedDelayTasks;
private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<>(16);
private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<>(16);
可见该类就是分类的多个容器,里面存储着解析后的任务对象,该类实现了InitializingBean接口,在生命周期中会执行afterPropertiesSet方法:
public void afterPropertiesSet() {
scheduleTasks();
}
protected void scheduleTasks() {
//若调度器为指定
if (this.taskScheduler == null) {
//默认的线程池是单线程线程池
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
//真正的添加待执行任务
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
addScheduledTask(scheduleFixedDelayTask(task));
}
}
}
我们可以测试下默认情况是否是单线程:
新增定时任务2
@Component
public class Sch02 {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Scheduled(cron = "* * * * * ?")
public void sch002() {
logger.info("hello 2");
}
}
启动后输出:发现是同一个线程在执行[pool-1-thread-1]
2021-07-21 22:27:17.004 INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch02 : hello 2
2021-07-21 22:27:17.004 INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:27:18.006 INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch02 : hello 2
2021-07-21 22:27:18.006 INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
2021-07-21 22:27:19.006 INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch02 : hello 2
2021-07-21 22:27:19.006 INFO 448 [pool-1-thread-1] com.boot.demoboot.Sch01 : hello 1
....
发现是同一个线程在执行[pool-1-thread-1],可见平时生产开发中一定要注意配置自定义线程池来执行定时任务,特别是对于io密集型任务,若是单线程则可能阻塞其他任务无法执行。
我们继续回过头看看ScheduledAnnotationBeanPostProcessor类,其实现了SmartInitializingSingleton在生命周期中会调用afterSingletonsInstantiated方法。
public void afterSingletonsInstantiated() {
// Remove resolved singleton classes from cache
this.nonAnnotatedClasses.clear();
if (this.applicationContext == null) {
// Not running in an ApplicationContext -> register tasks early...
finishRegistration();
}
}
重点是finishRegistration方法的执行
private void finishRegistration() {
//设置调度器
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
if (this.beanFactory instanceof ListableBeanFactory) {
//查找扩展配置类SchedulingConfigurer
Map<String, SchedulingConfigurer> beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(configurers);
for (SchedulingConfigurer configurer : configurers) {
//循环回调并将注册中心实例传入,由此我们可以自定义配置类,对注册中心注册的任务进行增删改查,达到自己的目的
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) {
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
}
}
catch (NoSuchBeanDefinitionException ex) {
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
}
}
catch (NoSuchBeanDefinitionException ex2) {
// Giving up -> falling back to default scheduler within the registrar...
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
//开启注册中心的调度任务
this.registrar.afterPropertiesSet();
}
这里重点在于开放了任务注册中心的扩展点SchedulingConfigurer,我们可以对任务注册中心进行自定义修改,如执行任务的线程池,调度器,添加任务,获取任务,甚至可以反射删除任务等等,详见:彻底关闭Schedule定时任务
示例:自定义任务调度器,配置线程池
@Component
public class SchConf implements SchedulingConfigurer {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
Set<ScheduledTask> scheduledTasks = taskRegistrar.getScheduledTasks();
logger.info("所有任务: {}", scheduledTasks);
//设置调度器
ConcurrentTaskScheduler taskScheduler = new ConcurrentTaskScheduler();
taskScheduler.setScheduledExecutor(Executors.newScheduledThreadPool(20));
taskRegistrar.setScheduler(taskScheduler);
}
}
最后:
在平时的生产开发中,现阶段已经流行微服务集群等概念,项目内直接配置定时任务的方式愈发少见了,而是采用一些三方任务调度框架如xxl-job,elastic-job等等有更好的分布式支持特性。