Spring Boot Schedule
Spring Boot 定时任务,常用的几种任务调度:
- Timer,简单无门槛,一般也没人用。
- spring @Scheduled 注解,一般集成于项目中,小任务很方便。
- 开源工具 Quartz,分布式集群开源工具,以下两个分布式任务应该都是基于Quartz实现的,可以说是中小型公司必选,当然也视自身需求而定。
- 分布式任务 XXL-JOB,是一个轻量级分布式任务调度框架,支持通过 Web 页面对任务进行 CRUD 操作,支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,支持在线配置调度任务入参和在线查看调度结果。
Timer
- 按照固定的频率去执行一个任务,不能指定时间
public class TestTimer {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("task run:"+ new Date());
}
};
Timer timer = new Timer();
// 安排指定的任务在指定的时间开始进行重复的固定延迟(10毫秒)执行。这里是每3秒执行一次
timer.schedule(timerTask, 10, 3000);
}
}
ScheduledExecutorService
和 timer 类似
public class TestScheduledExecutorService {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
// 参数:1、任务体 2、首次执行的延时时间
// 3、任务执行间隔 4、间隔时间单位
service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+new Date()), 0, 3, TimeUnit.SECONDS);
}
}
@Sechedule 注解标记的定时任务
- @EnableScheduling 在配置类上使用,开启计划任务的支持(类上)
- @Scheduled 来声明这是一个任务,包括 cron, fixDelay, fixRate 等类型(方法上,需先开启计划任务的支持)
- fixedRate ( 开始 - 开始 )是配置上一次任务执行开始到下一次执行开始的时间间隔,不会等待上一次任务执行完成就会调度下一次任务,将其放入等待队列中。
- fixedDelay ( 结束 - 开始 )是配置的上一次任务执行结束到下一次执行开始的间隔时间,也就是说会等待上一次任务执行结束后,延迟间隔时间,再执行下一次任务。
- cron 任务执行的cron表达式 0/1 * * * * ?
- zone cron表达式解析使用的时区,默认为服务器的本地时区,使用java.util.TimeZone#getTimeZone(String)方法解析 GMT-8:00
- fixedDelay 上一次任务执行结束到下一次执行开始的间隔时间,单位为ms 1000
- fixedDelayString 上一次任务执行结束到下一次执行开始的间隔时间,使用java.time.Duration#parse解析 PT15M
- fixedRate 以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务 2000
- fixedRateString 与fixedRate逻辑一致,只是使用java.time.Duration#parse解析 PT15M
- initialDelay 首先任务执行的延迟时间 1000
- initialDelayString 首次任务执行的延迟时间,使用java.time.Duration#parse解析 PT15M
@Sechedule(cron = "", zone="")
@Sechedule(fixedDelay = "", zone="")
@Sechedule(fixedDelayString = "", zone="")
@Sechedule(fixedRate = "", zone="")
@Sechedule(fixedRateString = "", zone="")
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
String CRON_DISABLED = "-";
String cron() default ""; //类似于corn表达式,可以指定定时任务执行的延迟及周期规则
String zone() default ""; //指明解析cron表达式的时区。
long fixedDelay() default -1; // end - start 在最后一次调用结束和下一次调用开始之间以固定周期(以毫秒为单位)执行带注解的方法。(要等待上次任务完成后)
String fixedDelayString() default ""; //同上面作用一样,只是String类型
long fixedRate() default -1; // start - start 在调用之间以固定的周期(以毫秒为单位)执行带注解的方法。(不需要等待上次任务完成)
String fixedRateString() default ""; //同上面作用一样,只是String类型
long initialDelay() default -1; //第一次执行fixedRate()或fixedDelay()任务之前延迟的毫秒数 。
String initialDelayString() default ""; //同上面作用一样,只是String类型
}
cron 表达式格式:
{秒数} {分钟} {小时} {日期} {月份} {星期} {年份(可为空)}
{0-59} {0-59} {0-23} {1-31} {1-12 or JAN-DEC} {1-7 or SUN-SAT} {empty,1970-2099}
{, - * /} {, - * ? / L W C} {, - * ? / L C #} {, - * /}
# 每隔5秒执行一次
@Scheduled(cron = "*/5 * * * * ?")
# 每隔1分钟执行一次
@Scheduled(cron = "0 */1 * * * ?")
# 每天23点执行一次
@Scheduled(cron = "0 0 23 * * ?")
# 每天凌晨1点执行一次
@Scheduled(cron = "0 0 1 * * ?")
# 每月1号凌晨1点执行一次
@Scheduled(cron = "0 0 1 1 * ?")
# 每月最后一天23点执行一次;
@Scheduled(cron = "0 0 23 L * ?")
# 每周星期天凌晨1点执行一次
@Scheduled(cron = "0 0 1 ? * L")
# 在26分、29分、33分执行一次
@Scheduled(cron = "0 26,29,33 * * * ?")
# 每天的0点、13点、18点、21点都执行一次
@Scheduled(crom = "0 0 0,13,18,21 * * ?")
,
:表示列出枚举值。 例如在 分 使用5,20,则意味着在5和20分 每分钟触发一次。-
:表示范围。 例如在 分 使用5-20,表示从5分到20分钟每分钟触发一次。*
:表示匹配该域的任意值。 例如在 分 使用*,即表示每分钟都会触发事件。/
:表示起始时间开始触发,然后每隔固定时间触发一次。 例如在分域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次。?
:只能用在周和日。它也匹配域的任意值,但是实际不会,因为周和日会相互影响。 例如在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法:13 13 15 20 * ?,其中最后一位只能用?,而不能使用*,如果使用 *表示不管星期几都会触发,实际上并不是这样。L
:表示最后,只能出现日和周。 例如在 日 使用 5L,意味着在最后的一个星期四触发。W
:表示有效工作日(周一到周五),只能出现在周域,系统将在离指定日期的最近的有效工作日除法事件。 例如:在 日 使用 5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份。#
:用于确定每个月第几个星期几,只能出现在周。 例如在4#2,表示某月的第二个星期三。LW
:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
Spring 动态修改 Cron
优点:能够动态修改
缺点:单一定时任务
- 实现动态定时任务,就需要借助 Spring 的 SchedulingConfigurer 接口,该方式可以实现动态时间,定时任务。
- 在 configureTasks 方法中,ScheduledTaskRegistrar 通过 addTriggerTask 来添加触发器任务,从而去执行。
- addTriggerTask 方法有两个参数,一个是要执行得任务,一个是触发器。
@Component
public class CronSchedule implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
// 通过数据库获取所有任务,所有任务通过
List<ScheduleSetting> scheduleList = xxxxxxMapper.getScheduleList();
System.out.println(scheduleList.size());
if (CollectionUtils.isNotEmpty(scheduleList)){
for (ScheduleSetting s : scheduleList){
scheduledTaskRegistrar.addTriggerTask(getRunnable(s), getTrigger(s));
}
}
}
/**
* 转换首字母小写
*
* @param str
* @return
*/
public static String lowerFirstCapse(String str) {
char[] chars = str.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
private Runnable getRunnable(ScheduleSetting scheduleSetting){
return new Runnable() {
@Override
public void run() {
Class<?> clazz;
try {
clazz = Class.forName(scheduleSetting.getBeanName());
String className = lowerFirstCapse(clazz.getSimpleName());
Object bean = SpringContext.getBean(className);
Method method = ReflectionUtils.findMethod(bean.getClass(), scheduleSetting.getMethodName());
if (StringUtils.isNotEmpty(scheduleSetting.getMethodParams())) {
ReflectionUtils.invokeMethod(method, bean, scheduleSetting.getMethodParams());
} else {
ReflectionUtils.invokeMethod(method, bean);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
};
}
private Trigger getTrigger(ScheduleSetting scheduleSetting){
return new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
CronTrigger trigger = new CronTrigger(scheduleSetting.getCronExpression());
Date nextExec = trigger.nextExecutionTime(triggerContext);
return nextExec;
}
};
}
}
# task-config.ini:
printTime.cron=0/10 * * * * ?
@Data
@Slf4j
@Component
@PropertySource("classpath:/task-config.ini")
public class ScheduleTask implements SchedulingConfigurer {
@Value("${printTime.cron}")
private String cron;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 动态使用cron表达式设置循环间隔
taskRegistrar.addTriggerTask(new Runnable() {
@Override
public void run() {
log.info("Current time: {}", LocalDateTime.now());
}
}, new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
// 任务触发,可修改任务的执行周期.
// 每一次任务触发,都会执行这里的方法一次,重新获取下一次的执行时间
// 使用CronTrigger触发器,可动态修改cron表达式来操作循环规则
// CronTrigger cronTrigger = new CronTrigger(cron);
// Date nextExecutionTime = cronTrigger.nextExecutionTime(triggerContext);
// 使用不同的触发器,为设置循环时间的关键,区别于CronTrigger触发器,该触发器可随意设置循环间隔时间,单位为毫秒
PeriodicTrigger periodicTrigger = new PeriodicTrigger(timer);
Date nextExecutionTime = periodicTrigger.nextExecutionTime(triggerContext);
return nextExecutionTime;
}
});
}
}
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
private final ScheduleTask scheduleTask;
@Autowired
public TestController(ScheduleTask scheduleTask) {
this.scheduleTask = scheduleTask;
}
@GetMapping("/updateCron")
public String updateCron(String cron) {
log.info("new cron :{}", cron);
scheduleTask.setCron(cron);
return "ok";
}
@GetMapping("/updateTimer")
public String updateTimer(Long timer) {
log.info("new timer :{}", timer);
scheduleTask.setTimer(timer);
return "ok";
}
}
Spring Boot 集成 Quartz
SQL 文件路径:quartz-2.3.2.jar!\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql
基本概念
-
任务 Job
- 工作任务调度的接口,每一个
job
必须实现org.quartz.job
接口,且只需实现接口定义的execute()
方法 - 实例在
Quartz
中的生命周期中- 每次调度器执行
job
时它在调用execute
方法前,会创建一个新的job
实例 - 当调用完成后,关联的
job
对象实例会被是释放,释放的实例会被垃圾回收机制回收
- 每次调度器执行
- 工作任务调度的接口,每一个
-
触发器 Trigger
- 负责触发任务
- 主要包含两种:
SimpleTrigger
和CronTriggerr
-
调度器 Schedule
Scheduler
是任务的调度器,会将任务job
和触发器Trigger
结合,负责基于Trigger
设定的时间执行job
集成步骤
- 引入依赖
<!-- quartz定时任务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
- 新建
Job
任务- 这里需要注意使用的是 Job 类,不能使用自定义方法,需要后边修改。
@Slf4j
public class CustomJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("Hello Job执行时间: " + new Date());
}
}
- 定义一个定时任务
public class QuartzTest {
public static void main(String[] args) throws Exception {
// 1. 创建一个JobDetail,把实现了Job接口的类邦定到JobDetail 构建者模式 绑定job withIdentity这里起一个唯一的名字
JobDetail jobDetail = JobBuilder.newJob(CustomJob.class)
.withIdentity("job1") // 唯一标识
.build();
// 2.创建一个Trigger触发器的实例,定义该job立即执行,并且每2秒执行一次,一直执行 repeatForever重复
SimpleTrigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("trigger1") // 唯一标识
.startNow() // 立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2) // 每2秒执行一次
.repeatForever()) // 一直重复执行
.build();
// SimpleTrigger 简单触发器
Date startTime = DateBuilder.nextGivenSecondDate(new Date( ),15); // 在当前时间15秒后运行
// 创建一个SimpleTrigger实例,指定该Trigger在Scheduler中所属组及名称。
// 接着设置调度的时间规则.当前时间15秒后运行,每10秒运行一次,共运行15次
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
.startAt(startTime).withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(15)
).build();
// CronTrigger - Cron触发器
// 每两秒执行
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").withSchedule(
CronScheduleBuilder.cronSchedule("/2 * * * * ?")
).build();
// 日期触发
// 每一分钟执行
DailyTimeIntervalTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").withSchedule(
DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule().withInterval(1, DateBuilder.IntervalUnit.MINUTE)
).build();
// 日历触发
// 每两秒执行
CalendarIntervalTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").withSchedule(
CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(2, DateBuilder.IntervalUnit.SECOND)
).build();
// 3.创建schedule调度器并执行 StdSchedulerFactory 工厂模式
StdSchedulerFactory factory = new StdSchedulerFactory();
// 获取调度器实例
Scheduler scheduler = factory.getScheduler();
// 开启调度器
scheduler.start();
// 把SimpleTrigger和JobDetail注册给调度器,通过触发器开始每2秒一次重复执行。
scheduler.scheduleJob(jobDetail, trigger);
// 关闭调度器
// scheduler.shutdown();
}
}
- 面向对象,定时任务对象
- CustomJob.java jobClassName 对应执行的方法(面前是类名/类路径,后续增加 Bean)
- 任务 JobDetail
- name 任务名称 jobName
- group 任务分组 jobGroup
- JobBuilder 是⽣成 JobDetail 的唯⼀标识 JobKey,暂时保存在 JobBuilder 中
- 触发器 Trigger
- name 任务名称 jobName
- group 任务分组 jobGroup
- TriggerBuilder ⽣成 Trigger 的唯⼀标识 TriggerKey,暂时保存在 TriggerBuilder 中
- startNow() 从当前时间开始
- startAt() 传⼊的时间早于cron表达式设置的时间,则将会触发⼀次,但不管cron表达式是否会多次执⾏,都只会触发⼀次
- endAt() 结束
- withSchedule
- SimpleScheduleBuilder 简单的触发器
- RepeatForever:指定触发器将无限期重复
- WithRepeatCount:指定重复次数
- CalendarIntervalScheduleBuilder 日期触发器
- WithInterval:指定要生成触发器的时间单位和间隔
- WithIntervalInHours:指定要生成触发器的间隔按小时来
- WithIntervalInMinutes:指定要生成触发器的间隔按分钟来
- WithIntervalInSeconds:指定要生成触发器的间隔按秒来
- WithIntervalInDays:指定要生成触发器的间隔按日来
- WithIntervalInWeeks:指定要生成触发器的间隔按周来
- WithIntervalInMonths:指定要生成触发器的间隔按月来
- WithIntervalInYears:指定要生成触发器的间隔按年来
- DailyTimeIntervalScheduleBuilder 日期触发类(日)
- CronScheduleBuilder 基于Cron表达式的Trigger,使⽤最多、功能最强⼤的Trigger。所以对象 cronException CRON表达式
- SimpleScheduleBuilder 简单的触发器
- WithDescription(“remark”) 添加说明
- UsingJobData:添加Job数据,底层是key-vaule的JobDataMap,和JobBuilder里面的一样
- ForJob:指定对应关系的JobKey,四个重载方法,底层都是指定一个JobKey
@Data
public class JobInfo{
private String cronExpression;
private String jobClassName;
private String triggerGroupName;
private String triggerName;
private String jobGroupName;
private String jobName;
private Date nextFireTime;
private Date previousFireTime;
private Date startTime;
private String timeZone;
private String status;
private String remark;
}
// 创建一个触发器
var trigger = TriggerBuilder.Create().Build();
// Trigger立即触发
TriggerBuilder.newTrigger().startNow().build();
// Trigger指定时间触发
TriggerBuilder.newTrigger().startAt(time).build();
// Trigger指定周期时间触发,即corn方式
TriggerBuilder.newTrigger().startNow().endAt(endTime)
.withSchedule(dailyAtHourAndMinute(start.getHours(), start.getMinutes()))
.build();
Spring Boot 集成 XXL-JOB
部署项目 XXL-JOB
XXL-JOB 分布式任务调度中心
项目源码
-
- 执行
/xxl-job/doc/db
目录下的数据库初始化脚本
- 执行
-
- 修改数据库配置
-
- 启动运行
-
- 访问
http://127.0.0.1:9080/xxl-job-admin
- 访问
-
- 用户密码分别为:admin/123456
Spring Boot 集成
- 引入依赖
<!-- xxl-job-core 分布式任务定时调度工具 -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.1</version>
</dependency>
-
启动类上添加注解
@EnableScheduling
-
添加配置类
@Configuration
@RequiredArgsConstructor
public class BeanConfig {
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
// 管理系统地址
xxlJobSpringExecutor.setAdminAddresses("http://127.0.0.1:8080/xxl-job-admin");
// 执行器的名称
xxlJobSpringExecutor.setAppname("xxlJob");
// 地址
// xxlJobSpringExecutor.setAddress(address);
// xxlJobSpringExecutor.setIp(ip);
// 内部连接使用的端口 通过 38801 与 XXL-JOB 管理系统关联
xxlJobSpringExecutor.setPort(38801);
// xxlJobSpringExecutor.setAccessToken(accessToken);
// 日志文件路径
xxlJobSpringExecutor.setLogPath("logs/xxl-job/first-study");
// 日志保留 7 天
xxlJobSpringExecutor.setLogRetentionDays(7);
return xxlJobSpringExecutor;
}
}