点赞多大胆,就有多大产!路漫漫其修远兮,吾将上下而求索,开源促使进步,献给每一位技术使用者和爱好者!
干货满满,摆好姿势,点赞发车,学习知识
定时任务介绍
什么是定时任务
大部分项目都会使用到定时任务这个功能,拿商城订单来说,当你下单之后如果没有付款,后台就会插入一条待支付的task(job),一般是30分钟,超过30min后就会执行这个job,去判断你是否支付,如果30分钟后没有支付则取消这个订单,定时任务在项目中使用场景非常多,比如:邮件定时发送,优惠券到期提醒等等场景。
常用定时工具
Java 的Timer
这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少
Spring Task
Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多
Quartz
这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,用的比较多的定时任务工具
Quartz
什么是Quartz
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目。
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制
Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
Quartz 允许程序开发人员根据时间的间隔来调度作业。
Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
Quartz核心概念
我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。
- Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)
- JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
- Trigger 代表一个调度参数的配置,什么时候去调。包括SimpleTrigger和CronTrigger
- Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了
- 所以我们使用Quartz时需要创建一个Job、使用Trigger来定义调用时间和策略、通过Scheduler来调度任务。
Quartz运行环境
- Quartz 可以运行嵌入在另一个独立式应用程序。
- Quartz 可以在应用程序服务器(或 servlet 容器)内被实例化,并且参与 XA 事务。
- Quartz 可以作为一个独立的程序运行(其自己的 Java 虚拟机内),可以通过 RMI 使用。
- Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行
Quartz入门案例
我们这里的案例每1秒调用一次任务,执行1分钟后结束
导入依赖
<!--quzrtz依赖-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
创建Job
package com.stt.springbootquartz.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* ClassName: PrintWordJob
* Description: 打印数据
* date: 2019/11/14 0014 下午 22:40
*
* @author stt
* @since JDK 1.8
*/
public class PrintWordJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
//打印时间
System.out.println(printTime);
}
}
创建Schedule
package com.stt.springbootquartz.schedule;
import com.stt.springbootquartz.job.PrintWordJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
/**
* ClassName: MyScheduler
* Description:
* date: 2019/11/14 0014 下午 22:43
*
* @author stt
* @since JDK 1.8
*/
public class MyScheduler {
public static void main(String[] args) throws SchedulerException, InterruptedException {
//1、创建调度器Scheduler
StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//2、创建JobDetail实例、b并与PrintWordJob绑定
JobDetail jobDetail = JobBuilder.newJob(PrintWordJob.class).withIdentity("job1", "group1").build();
//3、创建Trigger实例,每隔1秒执行一次
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()//立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)//每秒执行一次
.repeatForever())//一直执行
.build();
//4、执行
scheduler.scheduleJob(jobDetail,trigger);
System.out.println("调度器开始");
scheduler.start();
//睡眠
TimeUnit.MINUTES.sleep(1);
scheduler.shutdown();
System.out.println("调度器关闭");
}
}
Quartz API
描述
Quartz API的关键接口是:
- Scheduler - 与调度程序交互的主要API。
- Job - 由希望由调度程序执行的组件实现的接口。
- JobDetail - 用于定义作业的实例。
- Trigger(即触发器) - 定义执行给定作业的计划的组件。
- JobBuilder - 用于定义/构建JobDetail实例,用于定义作业的实例。
- TriggerBuilder - 用于定义/构建触发器实例。
Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)。
Job和JobDetail
可以看到,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分
为什么设计成JobDetail + Job,不直接使用Job,因为JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,Sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
JobDataMap
JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap,如下示例:
JobExecutionContext
JobExecutionContext中包含了Quartz运行时的环境以及Job本身的详细数据信息。当Schedule调度执行一个Job的时候,就会将JobExecutionContext传递给该Job的execute()中,Job就可以通过JobExecutionContext对象获取信息
Trigger
Trigger是Quartz的触发器,会去通知Scheduler何时去执行对应Job。
new Trigger().startAt():表示触发器首次被触发的时间;
new Trigger().endAt():表示触发器结束触发的时间;
SimpleTrigger
SimpleTrigger可以实现在一个指定时间段内执行一次作业任务或一个时间段内多次执行作业任务。下面提供几个案例
指定时间开始触发,不重复
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // 定义时间
.forJob("job1", "group1")
.build();
指定时间触发,每隔10秒执行一次,重复10次
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // i如果没有给出开始时间(如果忽略了这一行),则暗示“now”
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)) // 10次重复将产生11次发射
.forJob(myJob) //标记作业
.build();
5分钟以后开始触发,仅执行一次:
trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger5", "group1")
.startAt(futureDate(5, IntervalUnit.MINUTE)) // 使用DateBuilder在将来创建一个日期
.forJob(myJobKey)
.build();
立即触发,每个5分钟执行一次,直到22:00:
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever())
.endAt(dateOf(22, 0, 0))
.build();
建立一个触发器,将在下一个小时的整点触发,然后每2小时重复一次:
trigger = newTrigger()
.withIdentity("trigger8")
.startAt(evenHourDate(null))
.withSchedule(simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
.build();
scheduler.scheduleJob(trigger, job);
SpringBoot整合Quartz
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
任务类
public class TestJob extends QuartzJobBean {
private static final Logger log = LoggerFactory.getLogger(TestJob.class);
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("调用。。。。。。。。。");
}
}
定时任务配置类
@Configuration
public class QuartzConfiguration {
@Bean
public JobDetail testJobDetail(){
return JobBuilder.newJob(TestJob.class).withIdentity("testJob")
.storeDurably().build();
}
@Bean
public Trigger testTrigger(){
//设置定时策略
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever();
//创建Trigger对象
return TriggerBuilder.newTrigger().
forJob(testJobDetail()).withIdentity("testTrigger").withSchedule(scheduleBuilder).build();
}
}
到这里大家启动SpringBoot项目,控制台每2秒就会输出一个 调用。。。