本文已参与「新人创作礼」活动,一起开启掘金创作之路。
架构核心分析
SchedulerFactory (调度程序工厂):
- StdSchedulerFactory:Quartz默认的SchedulerFactory (默认实例化new StdSchedulerFactory())
- DirectSchedulerFactory:DirectSchedulerFactory是对SchedulerFactory的直接实现,通过它可以直接构建Scheduler、threadpool等
- ThreadExecutor / DefaultThreadExecutor:内部线程操作对象;
JobExecutionContext:Job上下文
- 保存着Trigger、JobDeaitl等信息,Job的execute方法传递的参数就是对象的实例;
- JobExecutionContextImpl
Scheduler:调度器
- StdScheduler:Quartz默认的Scheduler
- RemoteScheduler:带有RMI功能的Scheduler
Job:任务对象
- JobDetail:实现轮询的一个回调类,可将参数封装成JobDataMap对象,Quartz将任务的作业状态保存在JobDetail中
- JobDataMap:JobDataMap用来报错由JobDetail传递过来的任务实例对象
Trigger:触发器
- SimpleTrigger <普通的Trigger> :SimpleScheduleBuilder
- CronTrigger <带Cron Like 表达式的Trigger> :CronScheduleBuilder
- CalendarIntervalTrigger <带日期触发的Trigger>:CalendarIntervalScheduleBuilder
- DailyTimeIntervalTrigger <按天触发的Trigger> :DailyTimeIntervalScheduleBuilder
ThreadPool:为Quartz运行任务时提供了一些线程
- SimpleThreadPool:一个Quartz默认实现的简单线程池,它足够健壮,能够应对大部分常用场景
xxxListener:监听器
- JobListener
- TriggerListener
- SchedulerListener
入门案例
创建HelloJob任务类
HelloJob.java
//定义任务类
public class HelloJob implements Job{
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
// 定义时间
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = dateFormat.format(date);
// 定义工作任务内容
System.out.println("进行数据库备份操作,当前任务执行时间:"+dateString);
}
}
复制代码
创建任务调度类HelloSchedulerDemo
HelloSchedulerDemo.java
public class HelloSchedulerDemo {
public static void main(Stringp[] args) throws Exception{
//1:从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") //定义该实例唯一标识
.build();
//3:定义触发器,马上执行,然后每5秒重复执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定义该实例唯一标识
.startNow() // 马上执行
.withScheduler(SimpleSchedulerBuilder.simpleScheduler()
.repeatSecondlyForever(5)) // 每5秒执行一次
.build();
//4:使用触发器调度任务的执行
scheduler.schedulerJob(job, trigger);
//5:开启
scheduler.start();
//6:关闭
scheduler.shutdown();
}
}
复制代码
Job和JobDetail介绍
- Job:工作任务调度的接口,任务类需要实现该接口。该接口中定义execute方法,类似JDK提供的TimeTask类的run方法。在里面编写任务执行的业务逻辑。
- Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法前会创建一个新的Job实例,当调用完成后,关联的Job对象实例会被释放,释放的实例会被垃圾回收机制回收。
- JobDetail:JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。
- JobDetail重要属性:name、group、jobClass、jobDataMap。
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") // 定义该实例唯一标识,并指定一个组
.build();
System.out.println("name:"+job.getKey().getName());
System.out.println("group:"+job.getKey().getGrouop());
System.out.println("jobClass:"+job.getJobClass().getName());
复制代码
JobExecutionContext介绍
- 当Scheduler调用一个Job,就会将JobExecutionContext传递给Job的execute()方法;
- Job能通过JobExecution Context对象访问到Quartz运行时候的环境以及Job本身的明细数据。
HelloJob.java
@Override
public void execute(JobExecutionContext context) throws JobExecutionException{
...
// 获取JobDetailde内容
JobKey jobKey = context.getJobDetail().getKey();
System.out.println("工作任务名称:"+jobKey.getName()+",工作任务的组"+jobKey.getGroup());
System.out.println("任务类的名称(带路径):"+context.getJobDetail().getJobClass().getName());
System.out.println("任务类的名称:"+context.getJobDetail().getJobClass().getSimpleName());
// 获取Trigger的内容
TriggerKey triggerKey = context.getTrigger().getKey();
System.out.println("触发器名称:"+triggerKey.getName()+",触发器的组"+triggerKey.getGroup());
// 获取其他内容
System.out.println(context.getJobRunTime()); // 获取任务运行时间,long类型
System.out.println(context.getFireTime()); // 获取当前任务的执行时间,Date类型
System.out.println(context.getNextFireTime()); // 获取下一次任务的执行时间,Date类型
...
}
复制代码
JobDataMap介绍
1、使用Map获取
- 在进行任务调度时,JobDataMap存储在JobExecutionContext中,非常方便获取。
- JobDataMap可以用来装载任何可序列化的数据对象,当Job实例对象被执行时这些参数对象会传递给它。
- JobDataMap实现了JDK的Map接口,并且添加了非常方便的方法用来存取基本数据类型。
HelloSchedulerDemo.java
// 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1")
.usingJobData("message", "打印日志") // 传递参数:名称message
.build();
// 3:定义触发器,马上执行,然后每5秒重复执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withScheduler(SimpleSchedulerBuilder.simpleScheduler()
.repeatSecondlyForever(5))
.usingJobData("message", "simple触发器") // 传递参数:名称message
.build();
复制代码
HelloJob.java
@Override
public void execute(JobExecutionContext context) throws JobExecutionException{
...
// 获取传递的参数,从JobDetail中获取
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String jobDataMessage = jobDataMap.getString("message");
System.out.println("任务数据的参数值:"+jobDataMessage);
// 获取传ide参数,从Trigger中获取
JobDataMap triggerDataMap = context.getTrigger().getJobDataMap();
String triggerDataMessage = triggerDataMap.getString("message");
System.out.println("触发器数据的参数值:"+triggerDataMessage);
...
}
复制代码
2、Job实现类中添加setter方法对应JobDataMap的键值,Quartz框架默认的JobFactory实现类在初始化Job实例对象时会自动调用这些setter方法
HelloJob.java
private String message;
public void setMessage(String message){
this.message = message;
}
复制代码
注意:使用这种方式,如果遇到同名的key,Trigger中的usingJobData("message", "simple触发器")会覆盖JobDetail中的usingJobData("message", "打印日志")
有状态的Job和无状态的Job
@PersistJobDataAfterExecution注解的使用
有状态的Job可以理解为多次Job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中,而默认的无状态Job每次调用都会创建一个新的JobDataMap。
1、修改HelloSchedulerDemo.java,添加 .usingJobData("count", 0),标识计数器
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1")
.usingJobData("message", "打印日志") // 传递参数:名称message
.usingJobData("count", 0)
.build();
复制代码
2、修改HelloJob.java 添加count的setter方法
private Integer count;
public void setCount(Integer count){
this.count = count;
}
复制代码
在execute方法中添加下面部分
++count;
System.out.println("count的数量:"+count);
context.getJobDetail().getJobDataMap().put("count", count);
复制代码
HelloJob类没有添加@PersistJobDataAfterExecution注解,每次调用时都会创建一个新的JobDataMap,count不会累加;
HelloJob类添加@PersistJobDataAfterExecution注解,多次Job调用期间可以持有一些状态信息,即可以实现count的累加。