文章目录
- 前言
- [一、Scheduled 对cron 的解析](#一、Scheduled 对cron 的解析)
-
- [1.1 流程图:](#1.1 流程图:)
- [1.2 Scheduled 注解扫描](#1.2 Scheduled 注解扫描)
- [二、cron 定时任务的调度和执行](#二、cron 定时任务的调度和执行)
-
- [2.1 调度器启动](#2.1 调度器启动)
- [2.2 注册任务到调度器](#2.2 注册任务到调度器)
- [2.3 :调度器计算下次执行时间(CronTrigger 核心)](#2.3 :调度器计算下次执行时间(CronTrigger 核心))
- [2.4 线程池延迟执行任务(ScheduledExecutorService)](#2.4 线程池延迟执行任务(ScheduledExecutorService))
- [三、ThreadPoolTaskScheduler 和 延时队列](#三、ThreadPoolTaskScheduler 和 延时队列)
- 总结
前言
定时任务 @Scheduled 中配置cron 的表达式,但是spring 如何感知到 改任务的具体执行时机呢,本文对此进行分析。
一、Scheduled 对cron 的解析
1.1 流程图:
初始阶段:

任务触发:

任务执行:

1.2 Scheduled 注解扫描
项目启动时,@EnableScheduling 注解会导入 SchedulingConfiguration 配置类;
该配置类会注册 ScheduledAnnotationBeanPostProcessor(核心后置处理器),它的作用是扫描所有标注 @Scheduled 的方法。
(1) 启动类 @EnableScheduling 注解
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
@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() {
// bean 后置处理
return new ScheduledAnnotationBeanPostProcessor();
}
}
(2)对应@Scheduled 的方法扫描
ScheduledAnnotationBeanPostProcessor 中的 postProcessAfterInitialization 扫描对应的bean
java
public Object postProcessAfterInitialization(Object bean, String beanName) {
// ========== 步骤1:过滤特殊Bean,无需处理定时任务 ==========
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// 忽略AOP基础设施(如代理工厂)、任务调度器本身、JDK定时执行器
return bean;
}
// ========== 步骤2:获取Bean的最终目标类(穿透代理) ==========
// AopProxyUtils.ultimateTargetClass:获取代理对象背后的真实类(如CGLIB/JDK代理的目标类)
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
// ========== 步骤3:快速跳过无@Scheduled注解的类(缓存优化) ==========
// nonAnnotatedClasses:缓存已确认无@Scheduled注解的类,避免重复扫描
// AnnotationUtils.isCandidateClass:快速判断类是否可能包含@Scheduled注解(避免全量反射)
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, List.of(Scheduled.class, Schedules.class))) {
// ========== 步骤4:扫描类中所有标注@Scheduled的方法 ==========
// MethodIntrospector.selectMethods:遍历类的所有方法,筛选符合条件的方法
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
// 解析方法上的@Scheduled注解(支持@Schedules批量注解)
Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
// 有注解则返回,无则返回null(过滤掉无注解的方法)
return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
});
// ========== 分支A:无@Scheduled方法,缓存标记 ==========
if (annotatedMethods.isEmpty()) {
// 加入缓存,后续无需重复扫描该类
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
}
}
// ========== 分支B:有@Scheduled方法,处理并注册任务 ==========
else {
// 遍历所有标注了@Scheduled的方法 + 方法上的所有@Scheduled注解(一个方法可多个@Scheduled)
annotatedMethods.forEach((method, scheduledAnnotations) ->
scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
// ========== 步骤5:处理非单例Bean,标记需手动取消任务 ==========
// 判断条件:Bean不是单例 或 是FactoryBean创建的单例 → 需手动取消
if ((this.beanFactory != null &&
(!this.beanFactory.containsBean(beanName) || !this.beanFactory.isSingleton(beanName)) ||
(this.beanFactory instanceof SingletonBeanRegistry sbr && sbr.containsSingleton(beanName)))) {
// 将Bean加入手动取消列表,上下文关闭时触发cancel,避免内存泄漏
this.manualCancellationOnContextClose.add(bean);
}
}
}
// 返回原Bean(后置处理器不修改Bean本身,仅处理注解)
return bean;
}
(3)注解解析 :processScheduled
java
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
// ========== 步骤1:判断是否为响应式任务(核心前置条件) ==========
// REACTIVE_STREAMS_PRESENT:是否引入了Reactive Streams依赖(spring-webflux/reactor-core)
// ScheduledAnnotationReactiveSupport.isReactive(method):判断方法是否是响应式类型(返回Mono/Flux)
if (REACTIVE_STREAMS_PRESENT && ScheduledAnnotationReactiveSupport.isReactive(method)) {
// ========== 分支1:响应式任务 → 异步处理 ==========
// 1. 校验响应式方法合法性(如不能是Kotlin挂起函数,返回值必须是deferred Publisher)
// 2. 注册响应式定时任务(适配Reactor的调度逻辑)
processScheduledAsync(scheduled, method, bean);
return;
}
// ========== 分支2:普通同步任务 → 同步处理 ==========
// 处理传统定时任务(void返回值/非响应式返回值),如cron/fixedRate等
processScheduledSync(scheduled, method, bean);
}
private void processScheduledSync(Scheduled scheduled, Method method, Object bean) {
Runnable task;
try {
// 保证要调用的原始方法
task = createRunnable(bean, method, scheduled.scheduler());
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
method.getName() + "': " + ex.getMessage());
}
//
processScheduledTask(scheduled, task, method, bean);
}
(4)任务 的解析:
bash
private void processScheduledTask(Scheduled scheduled, Runnable runnable, Method method, Object bean) {
try {
// 标记是否处理了有效的调度属性(cron/fixedDelay/fixedRate)
boolean processedSchedule = false;
// 核心错误提示:cron/fixedDelay/fixedRate 必须且只能指定一个
String errorMessage = "Exactly one of the 'cron', 'fixedDelay' or 'fixedRate' attributes is required";
// 存储当前方法解析出的所有定时任务(一个方法可多个@Scheduled,每个注解对应一个任务)
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// ========== 步骤1:解析初始延迟(initialDelay) ==========
// 1.1 解析硬编码的initialDelay(如initialDelay = 1000,timeUnit = TimeUnit.MILLISECONDS)
Duration initialDelay = toDuration(scheduled.initialDelay(), scheduled.timeUnit());
// 1.2 解析字符串形式的initialDelay(支持占位符,如initialDelayString = "${initial.delay:1000}")
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {
// 校验:不能同时指定initialDelay(数字)和initialDelayString(字符串)
Assert.isTrue(initialDelay.isNegative(), "Specify 'initialDelay' or 'initialDelayString', not both");
// 解析占位符(如${initial.delay} → 实际值)
if (this.embeddedValueResolver != null) {
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
if (StringUtils.hasLength(initialDelayString)) {
try {
// 将字符串转为Duration(支持"1s"/"5m"等格式)
initialDelay = toDuration(initialDelayString, scheduled.timeUnit());
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\"; " + ex);
}
}
}
// ========== 步骤2:解析Cron表达式 ==========
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
String zone = scheduled.zone();
// 解析占位符(如cron = "${cron.expression}", zone = "${time.zone}")
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
if (StringUtils.hasLength(cron)) {
// 校验:cron不支持initialDelay(cron本身是基于时间表达式的,无需初始延迟)
Assert.isTrue(initialDelay.isNegative(), "'initialDelay' not supported for cron triggers");
processedSchedule = true; // 标记已处理有效调度属性
// 排除禁用cron的情况(cron = "-",即Scheduled.CRON_DISABLED)
if (!Scheduled.CRON_DISABLED.equals(cron)) {
// 创建CronTrigger(核心:解析cron表达式+时区)
CronTrigger trigger;
if (StringUtils.hasText(zone)) {
trigger = new CronTrigger(cron, StringUtils.parseTimeZoneString(zone));
}
else {
trigger = new CronTrigger(cron);
}
// 封装为CronTask并注册到调度器
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, trigger)));
}
}
}
// ========== 步骤3:处理初始延迟(统一为Duration.ZERO或实际值) ==========
// 若未指定initialDelay → 延迟0;指定了则用实际值(后续fixedDelay/fixedRate共用)
Duration delayToUse = (initialDelay.isNegative() ? Duration.ZERO : initialDelay);
// ========== 步骤4:解析fixedDelay(固定延迟) ==========
// 4.1 解析硬编码的fixedDelay(如fixedDelay = 5000)
Duration fixedDelay = toDuration(scheduled.fixedDelay(), scheduled.timeUnit());
if (!fixedDelay.isNegative()) {
// 校验:不能同时指定cron(processedSchedule=true)和fixedDelay
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
// 封装为FixedDelayTask并注册(固定延迟:上一次执行完成后延迟delayToUse执行)
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, delayToUse)));
}
// 4.2 解析字符串形式的fixedDelay(支持占位符,如fixedDelayString = "${fixed.delay:5000}")
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 = toDuration(fixedDelayString, scheduled.timeUnit());
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\"; " + ex);
}
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, delayToUse)));
}
}
// ========== 步骤5:解析fixedRate(固定频率) ==========
// 5.1 解析硬编码的fixedRate(如fixedRate = 3000)
Duration fixedRate = toDuration(scheduled.fixedRate(), scheduled.timeUnit());
if (!fixedRate.isNegative()) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
// 封装为FixedRateTask并注册(固定频率:上一次开始执行后延迟delayToUse执行)
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, delayToUse)));
}
// 5.2 解析字符串形式的fixedRate(支持占位符)
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 = toDuration(fixedRateString, scheduled.timeUnit());
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\"; " + ex);
}
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, delayToUse)));
}
}
// ========== 步骤6:处理一次性任务(仅指定initialDelay,无cron/fixedDelay/fixedRate) ==========
if (!processedSchedule) {
// 校验:一次性任务必须指定initialDelay
if (initialDelay.isNegative()) {
throw new IllegalArgumentException("One-time task only supported with specified initial delay");
}
// 封装为OneTimeTask(仅执行一次,延迟delayToUse)
tasks.add(this.registrar.scheduleOneTimeTask(new OneTimeTask(runnable, delayToUse)));
}
// ========== 步骤7:缓存注册的任务(用于上下文关闭时取消) ==========
synchronized (this.scheduledTasks) {
// 按Bean缓存任务列表:key=Bean实例,value=该Bean下的所有定时任务
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());
}
}
- processScheduledTask 是同步定时任务注册的最终入口,解析 @Scheduled 所有核心属性;
- 按「cron → fixedDelay → fixedRate → 一次性任务」的优先级解析,确保互斥性;
- 支持硬编码值和占位符解析,适配配置中心动态调整;
- 将不同类型的任务封装为 CronTask/FixedDelayTask/FixedRateTask/OneTimeTask,注册到调度器并缓存;
二、cron 定时任务的调度和执行
spring 把定时任务缓存到 Map<Object, Set> scheduledTasks 后,这些任务的执行过程未「任务注册→调度器启动→定时触发→任务执行→循环调度」,scheduledTasks 本质是「任务缓存容器」(用于管理 / 取消任务),而非直接触发执行的组件,真正的触发逻辑由 Spring 内置的 TaskScheduler 调度器实现
2.1 调度器启动
ThreadPoolTaskScheduler 会被初始化并启动,若未自定义调度器,Spring 会自动创建默认的 ThreadPoolTaskScheduler(核心线程数默认 1,可通过 spring.task.scheduling.pool.size 配置);调度器初始化时,会创建 ScheduledExecutorService(JDK 核心定时线程池),作为任务触发的底层线程池;
调度器启动后,监听「上下文刷新完成事件(ContextRefreshedEvent)」,确认所有任务注册完成,开始调度逻辑
调度器初始化 ExecutorConfigurationSupport #initialize
c
private @Nullable ExecutorService executor;
public void initialize() {
if (logger.isDebugEnabled()) {
logger.debug("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (!this.threadNamePrefixSet && this.beanName != null) {
setThreadNamePrefix(this.beanName + "-");
}
ThreadFactory factory = (this.virtualThreads ?
new VirtualThreadTaskExecutor(getThreadNamePrefix()).getVirtualThreadFactory() : this.threadFactory);
// 线程池初始化
this.executor = initializeExecutor(factory, this.rejectedExecutionHandler);
this.lifecycleDelegate = new ExecutorLifecycleDelegate(this.executor);
}
ThreadPoolTaskScheduler 初始化 ThreadPoolTaskScheduler #initializeExecutor
java
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
// 创建核心线程池
this.scheduledExecutor = createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler);
if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor threadPoolExecutor) {
if (this.removeOnCancelPolicy) {
threadPoolExecutor.setRemoveOnCancelPolicy(true);
}
if (this.continueExistingPeriodicTasksAfterShutdownPolicy) {
threadPoolExecutor.setContinueExistingPeriodicTasksAfterShutdownPolicy(true);
}
if (!this.executeExistingDelayedTasksAfterShutdownPolicy) {
threadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
}
}
return this.scheduledExecutor;
}
2.2 注册任务到调度器
java
// 注册CronTask到调度器
private @Nullable TaskScheduler taskScheduler;
public ScheduledTask scheduleCronTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask(cronTask);
// 核心:调度器根据CronTrigger计算下次执行时间,提交任务
scheduledTask.future = this.taskScheduler.schedule(
cronTask.getRunnable(), cronTask.getTrigger()
);
return scheduledTask;
}
2.3 :调度器计算下次执行时间(CronTrigger 核心)
java
// CronTrigger.nextExecutionTime() 核心逻辑
public Date nextExecutionTime(TriggerContext triggerContext) {
// 解析Cron表达式,计算下次执行时间
Instant timestamp = determineLatestTimestamp(triggerContext);
ZoneId zone = (this.zoneId != null ? this.zoneId : triggerContext.getClock().getZone());
ZonedDateTime zonedTimestamp = timestamp.atZone(zone);
ZonedDateTime nextTimestamp = this.expression.next(zonedTimestamp);
return (nextTimestamp != null ? nextTimestamp.toInstant() : null);
}
2.4 线程池延迟执行任务(ScheduledExecutorService)
ThreadPoolTaskScheduler #schedule
java
@Override
public @Nullable ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
ErrorHandler errorHandler = this.errorHandler;
if (errorHandler == null) {
errorHandler = TaskUtils.getDefaultErrorHandler(true);
}
// 封装任务为ScheduledFutureTask(实现Runnable+Future)
return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException(executor, task, ex);
}
}
public @Nullable ScheduledFuture<?> schedule() {
synchronized (this.triggerContextMonitor) {
this.scheduledExecutionTime = this.trigger.nextExecution(this.triggerContext);
if (this.scheduledExecutionTime == null) {
return null;
}
Duration delay = Duration.between(this.triggerContext.getClock().instant(), this.scheduledExecutionTime);
// 提交到线程池队列,等待延迟执行
this.currentFuture = this.executor.schedule(this, delay.toNanos(), TimeUnit.NANOSECONDS);
return this;
}
}
- scheduledTasks 是任务缓存容器,仅用于「注册 / 取消」任务,不参与触发执行;
- 任务触发的核心是 ThreadPoolTaskScheduler(封装 JDK 定时线程池),通过「触发器计算下次执行时间→线程池延迟执行→执行完成后重新调度」形成循环;
- 任务执行的本质是反射调用 @Scheduled 方法,执行完成后重新计算下次时间,直到应用停止
三、ThreadPoolTaskScheduler 和 延时队列
ThreadPoolTaskScheduler 是 Spring 对 JDK 定时线程池(ScheduledThreadPoolExecutor)的封装,而 ScheduledThreadPoolExecutor 的核心就是基于「延时队列(DelayedWorkQueue)」实现任务的延迟触发,因此 ThreadPoolTaskScheduler 本质是通过「延时队列」来管理定时任务的执行时机。
| 组件 | 角色定位 | 核心关系 |
|---|---|---|
| ThreadPoolTaskScheduler | Spring 封装的上层调度器(面向开发者) | 依赖 JDK ScheduledThreadPoolExecutor,间接使用其内置的延时队列 |
| 延时队列(DelayedWorkQueue | JDK ScheduledThreadPoolExecutor 内置的阻塞队列(底层数据结构) | 存储待执行的定时任务,按「下次执行时间」排序,是延迟触发的核心载体 |
| ScheduledThreadPoolExecutor | JDK 定时线程池(ThreadPoolTaskScheduler 的底层实现) | 核心逻辑:将任务放入延时队列,线程轮询队列,仅当任务到达执行时间才取出执行 |
- ThreadPoolTaskScheduler 的核心属性是 ScheduledExecutorService,而默认实现就是 ScheduledThreadPoolExecutor;
- ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的子类,它重写了「任务提交逻辑」和「队列类型」------ 强制使用内置的 DelayedWorkQueue(延时队列),且不允许外部替换;
- 队列中的元素是 ScheduledFutureTask(实现 Delayed 接口),队列会按「任务的下次执行时间(getDelay ())」从小到大排序,保证先到期的任务排在队首。
总结
spring通过 bean的后置处理器 扫描有@Scheduled 的注解并封装为ScheduledTask ,并通过对应的Trigger 触发器获取到下一次执行任务的时间,任务放入延时队列,线程轮询队列,仅当任务到达执行时间才取出执行 。