在日常Java开发中,定时任务是高频需求场景,比如定时同步数据、定时清理日志、定时发送通知等。Spring框架提供的**@Scheduled** 注解简化了定时任务的开发,无需依赖外部调度框架(如Quartz),就能快速实现轻量级定时任务。而cron表达式作为Schedule中灵活度最高的触发规则配置方式,是掌握该功能的核心。本文将从Schedule的基础用法入手,深入解析cron表达式,并结合实例演示其在项目中的应用。
一、Spring Schedule基础配置
要使用Spring Schedule,首先需要完成基础配置,整体步骤简单易懂,适用于Spring Boot项目和传统Spring项目。
1.1 依赖引入(Spring Boot)
Spring Boot项目无需额外引入依赖,spring-boot-starter中已包含Schedule相关组件,只需确保pom.xml(Maven)中存在核心依赖:
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
1.2 开启定时任务支持
在Spring Boot启动类上添加**@EnableScheduling**注解,开启定时任务调度功能:
java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // 开启定时任务
public class ScheduleDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleDemoApplication.class, args);
}
}
1.3 基础定时任务实现
通过**@Scheduled** 注解标记方法为定时任务,支持多种触发规则,常见的有固定延迟、固定频率和cron表达式三种方式。其中cron表达式适用于复杂的时间规则配置,也是本文重点。
java
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component // 交给Spring管理
public class ScheduleTask {
// 固定延迟:上一次任务执行完毕后延迟1秒执行下一次
@Scheduled(fixedDelay = 1000)
public void fixedDelayTask() {
System.out.println("固定延迟任务执行:" + System.currentTimeMillis());
}
// 固定频率:每隔1秒执行一次(无论上一次是否完成)
@Scheduled(fixedRate = 1000)
public void fixedRateTask() {
System.out.println("固定频率任务执行:" + System.currentTimeMillis());
}
// cron表达式:每天凌晨2点执行
@Scheduled(cron = "0 0 2 * * ?")
public void cronTask() {
System.out.println("Cron任务执行:" + System.currentTimeMillis());
}
}
二、Cron表达式深度解析
Cron表达式是一种时间表达式,用于定义复杂的定时规则,起源于Unix系统,后被广泛应用于各类调度框架。在Spring Schedule中,cron表达式支持6个或7个字段,字段之间用空格分隔,语法格式如下:
2.1 表达式格式
Spring Schedule中的cron表达式格式(7个字段,最后一个年份字段可选):
秒 分 时 日 月 周 年(可选)
各字段的取值范围及允许的特殊字符如下表所示:
| 字段 | 取值范围 | 允许的特殊字符 | 说明 |
|---|---|---|---|
| 秒 | 0-59 | , - * / | 每秒用*表示,每5秒用0/5表示 |
| 分 | 0-59 | , - * / | 每分用*表示,每10分用0/10表示 |
| 时 | 0-23 | , - * / | 每天凌晨2点用2表示,每6小时用0/6表示 |
| 日 | 1-31 | , - * / ? L W C | ?表示不指定值,避免日和周冲突;L表示当月最后一天 |
| 月 | 1-12 或 JAN-DEC | , - * / | 1表示1月,JAN也可表示1月 |
| 周 | 1-7 或 SUN-SAT | , - * / ? L C # | 1表示周日,7表示周六;#表示第几个周几,如6#3表示当月第3个周五 |
| 年(可选) | 1970-2099 | , - * / | 省略时表示所有年份 |
2.2 特殊字符含义
cron表达式的灵活度依赖于特殊字符的使用,核心特殊字符的含义如下:
-
*:表示匹配该字段的所有值。例如,时字段为*,表示每小时都触发。
-
?:仅用于日和周字段,标识不指定具体值,避免两个字段冲突。例如,要指定每月10号触发,周字段需设为?
-
/:表示递增触发。格式为"起始值/步长",例如,秒字段为0/5,表示从0秒开始,每5秒触发一次。
-
-:表示区间。例如,时字段为9-17,表示上午9点到下午5点之间,每小时触发一次。
-
,:表示多个值。例如,分字段为10,20,30,表示每小时的10分、20分、30分各触发一次。
-
L:仅用于日和周字段,标识最后一个。例如,日字段为L,表示当月最后一天;周字段为L,表示当月最后一个周六。
-
#:仅用于周字段,标识第几个周几。格式为"周几#第几个",例如,周字段为6#3,表示当月第3个周五(6表示周五)。
2.3 常用Cron表达式示例
结合实际业务场景,整理以下常用cron表达式,可直接套用:
-
0 0 2 * * ?:每天凌晨2点执行
-
0 30 8 * * ?:每天早上8点30分执行
-
0 0/10 * * * ?:每10分钟执行一次
-
0 0 9-18 * * ?:每天上午9点到下午6点,每小时执行一次
-
0 0 12 ? * SAT:每周六中午12点执行
-
0 0 0 L * ?:每月最后一天凌晨0点执行
-
0 0 0 1 * ?:每月1号凌晨0点执行
-
0 0 0 ? * 6#1:每月第一个周五凌晨0点执行
-
0/5 * * * * ?:每5秒执行一次(用于测试)
三、Schedule进阶用法与注意事项
3.1 异步定时任务
默认情况下,Spring Schedule的定时任务是同步执行的,即多个任务会在同一个线程中按顺序执行,若某个任务执行时间过长,会阻塞其他任务。如需异步执行,可通过@EnableAsync和@Async注解实现:
java
// 1. 启动类添加@EnableAsync开启异步支持
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class ScheduleDemoApplication { ... }
// 2. 定时任务方法添加@Async注解
@Component
public class ScheduleTask {
@Async
@Scheduled(cron = "0/5 * * * * ?")
public void asyncCronTask() {
System.out.println("异步Cron任务执行:" + Thread.currentThread().getName());
}
}
3.2 动态调整Cron表达式
静态cron表达式在项目启动后无法修改,若需动态调整触发规则,可通过实现SchedulingConfigurer接口自定义调度配置,从数据库或配置中心读取cron表达式:
java
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
@Component
public class DynamicCronTask implements SchedulingConfigurer {
// 模拟从数据库获取cron表达式(实际项目中可对接DB或Nacos)
private String cron = "0/5 * * * * ?";
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 注册定时任务
taskRegistrar.addTriggerTask(
this::dynamicTaskExecute, // 任务执行逻辑
triggerContext -> {
// 每次触发前重新获取cron表达式(实现动态更新)
CronTrigger trigger = new CronTrigger(getCron());
return trigger.nextExecutionTime(triggerContext);
}
);
}
public void dynamicTaskExecute() {
System.out.println("动态Cron任务执行:" + System.currentTimeMillis());
}
// 获取最新cron表达式(可扩展为从数据库查询)
public String getCron() {
return this.cron;
}
// 更新cron表达式
public void setCron(String cron) {
this.cron = cron;
}
}
3.3 核心注意事项
-
日和周字段冲突:日和周字段不能同时指定具体值,需有一个设为?,否则会导致任务触发规则异常。例如,要指定每月10号执行,周字段必须设为?,表达式为0 0 0 10 * ?。
-
任务执行时长:同步任务中,若任务执行时长超过触发间隔,下一次任务会等待上一次完成后再执行;异步任务无此限制,但需注意并发问题。
-
时区问题 :Spring Schedule默认使用服务器时区,若需指定时区,可在
@Scheduled注解中设置zone属性,例如zone = "GMT+8"。 -
避免耗时操作:定时任务中应避免执行耗时过长的操作,建议将耗时逻辑异步处理,或拆分任务,防止阻塞调度线程。