本文共 16341 字,大约阅读时间需要 54 分钟。
Quartz是一个完全由java编写的开源作业调度框架,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。
第一步、导入相应的jar包
org.quartz-scheduler quartz 2.3.2 org.quartz-scheduler quartz-jobs 2.3.2 org.slf4j slf4j-log4j12 2.0.0-alpha1 log4j log4j 1.2.17
第二步、新建一个任务类(Task/Job),实现Job接口,重写execute方法
public class helloJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String test = dateFormat.format(date); //工作内容 System.out.println("数据库备份成功,当前时间为:"+test); }}
第三步、编写调度类scheduler,编写任务实例和触发器,通过scheduler将触发器与任务绑定。
public class helloScheduler { public static void main(String[] args) throws SchedulerException { //调度器(Scheduler),通过工厂模式从工厂中获取调度实例 Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler(); //任务实例(JobDetail) JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口 .newJob(helloJob.class) .withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组 .build(); //触发器 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1")//参数一是触发器名称,参数二是触发器组,将触发器分组 .startNow()//马上启动触发器 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).build();//每1秒触发一次 //scheduler关联任务实例和触发器 defaultScheduler.scheduleJob(jobDetail, trigger); //启动 defaultScheduler.start(); }}
创建新的Job实例演示: 我们只需要在构造函数中输出一个字符串就好了,如下图所示: 结果: 这就说明在每次调度执行Job的时候,都会实例化一个Job类。 此外,JobDetail还有四个比较重要的属性:
- name:
- group
- jobClass
- jobDataMap 具体的作用看下图 输出结果:
Job能通过JobExecutionContext上下文对象访问到Quartz运行时候的环境以及Job本身的详细数据。
public class helloJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String test = dateFormat.format(date); //获取JobDetail的内容 JobKey jobKey = jobExecutionContext.getJobDetail().getKey(); System.out.println("工作任务名称:"+jobKey.getName()+"工作任务组:"+jobKey.getGroup()); System.out.println("任务类的名称(带路径):"+ jobExecutionContext.getJobDetail().getJobClass().getName()); System.out.println("任务类的名称:"+ jobExecutionContext.getJobDetail().getJobClass().getSimpleName()); //工作内容 System.out.println("数据库备份成功,当前时间为:"+test); }}
运行结果:
工作任务名称:job1工作任务组:group1任务类的名称(带路径):com.gdpu.quartz.job.helloJob任务类的名称:helloJob数据库备份成功,当前时间为:2020-11-30 10:45:45
通过JobDataMap,我们可以获取JobDetail,Trigger在scheduler中传递的参数,返回给任务类Job接收,具体的操作如下:
scheduler类:
public class helloScheduler { public static void main(String[] args) throws SchedulerException { //调度器(Scheduler),通过工厂模式从工厂中获取调度实例 Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler(); //任务实例(JobDetail) JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口 .newJob(helloJob.class) .withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组 .usingJobData("message","任务实例传递参数")//传递参数给Job任务类 .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1")//参数一是触发器名称,参数二是触发器组,将触发器分组 .startNow()//马上启动触发器 .usingJobData("message","触发器传递参数")//传递参数给Job任务类 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).build();//每1秒触发一次 //scheduler关联任务实例和触发器 defaultScheduler.scheduleJob(jobDetail, trigger); //启动 defaultScheduler.start(); }}
Job任务类:
public class helloJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String test = dateFormat.format(date); //获取Scheduler中JobDetail,trigger传递的参数 //获取JobDetail中传递的参数 JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap(); String message = jobDataMap.getString("message"); System.out.println("JobDetail中的参数:"+message); //获取Trigger中传递的参数 JobDataMap jobDataMap1 = jobExecutionContext.getTrigger().getJobDataMap(); String message1 = jobDataMap1.getString("message"); System.out.println("Trigger中的参数:"+message1); //工作内容 System.out.println("数据库备份成功,当前时间为:"+test); }}
同理,想要获取Trigger的内容的操作,也是同获取JobDetail的操作是类似的,包括获取Trigger的任务名组名等,这里就不展示了。通过:
获取其他内容,如任务的当前执行时间以及下次执行时间: 最后总结一点,如果我们觉得先通过Context上下文对象获取jobData,然后再获取JobMap这些方式很麻烦,如下展示的是从JobDetail获取dataMap,Trigger中获取的形式是一样的: 我们也可以在Job类定义一个相关类型的成员变量,例如我在Scheduler中JobDetail通过dataMap穿的value是String类型的,我在Job类就可以定义一个String类型的成员变量去接收,重写set方法即可。 但是这个方法如果遇到同名的key,则Trigger中的value会覆盖JobDetail中的value。TriggerKey key= jobExecutionContext.getTrigger().getKey();
即可。
什么是有状态的Job?
有状态的Job得意思其实就是每次调用Job的时候,只会有JobDataMap这一个实例Map,而无状态的Job在每次调用Job的时候都会新建一个新的JobDataMap,就会导致一些状态信息无法储存共享,其实这就有点像我们Bean的单例和多例模式,演示如下:
//任务实例(JobDetail) JobDetail jobDetail = JobBuilder//加载任务类,和具体任务绑定,该任务必须实现Job接口 .newJob(helloJob.class) .withIdentity("job1", "group1")//参数一是任务名称,唯一实例;参数二是任务组的名称,将任务分组 .usingJobData("count",0)//把数值传给Job接收 .build();
//Job任务类(Job)//演示无状态的Job ++count;//通过set方法直接获取到JobDetail传递过来的count数值,做加法运算后输出 System.out.println("当前的count数值为:"+count); jobDataMap.put("count",count);//将新增的数值增加后返回给JobDetail任务实例
//展示结果当前的count数值为:1数据库备份成功,当前时间为:2020-11-30 11:34:01当前的count数值为:1数据库备份成功,当前时间为:2020-11-30 11:34:02当前的count数值为:1数据库备份成功,当前时间为:2020-11-30 11:34:03
我们可以很明显的看到,结果一直都是1,因为这个时候我们的Job是无状态的,每一次DataMap都是一个新的实例。我们可以通过在Job类上加上这样一个注解即可保证DataMap一直都是同一个实例。
@PersistJobDataAfterExecution
加上这个注解之后,我们的Job就变成有状态的了。
Trigger的相关实现类:
通过simpleTrigger可以简单的设置一些设置,如开始时间,结束时间,执行次数,执行间隔等,从而完成在规定的一个时间段执行一些具体的任务,如:
Job类:
public class testSimpleTrigger implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String test = dateFormat.format(date); //获取trigger Trigger trigger = context.getTrigger(); //获取trigger实例,一个JobKey对应一个trigger实例 TriggerKey key = trigger.getKey(); System.out.println("任务开始时间为:"+dateFormat.format(trigger.getStartTime())); System.out.println("任务结束时间为:"+dateFormat.format(trigger.getEndTime())); System.out.println("Trigger名称为:"+key.getName()+"------------"+"Trigger组名称为:"+key.getGroup()); //工作内容 System.out.println("数据库备份成功,当前时间为:"+test); }}
scheduler类:
public class simpleTriggerScheduler { public static void main(String[] args) throws SchedulerException { //调度器(Scheduler),通过工厂模式从工厂中获取调度实例 Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler(); //设置任务开始时间 Date startDate = new Date(); startDate.setTime(startDate.getTime()+3000);//延迟三秒后开始任务 //设置任务结束时间 Date endDate = new Date(); endDate.setTime(endDate.getTime()+10000);//任务的结束时间延迟10秒 //任务实例(JobDetail) JobDetail jobDetail = JobBuilder .newJob(testSimpleTrigger.class) .withIdentity("job1", "group1") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .startNow()//马上启动触发器 .startAt(startDate) .endAt(endDate) //withRepeatCount表示执行次数,值得注意的是,当我们规定了指定的执行时间区间 //如果在时间区间结束了,执行任务次数没达到规定的五次,按照执行时间区间来算而不是按照执行次数来算 //表示每隔五秒执行一次,需要执行五次,但由于时间规定了只有十秒,因此最终只执行了两次 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(5)).build(); //scheduler关联任务实例和触发器 defaultScheduler.scheduleJob(jobDetail, trigger); //启动 defaultScheduler.start(); }}
输出如下:
任务开始时间为:2020-11-30 14:09:10任务结束时间为:2020-11-30 14:09:17Trigger名称为:trigger1------------Trigger组名称为:group1数据库备份成功,当前时间为:2020-11-30 14:09:10任务开始时间为:2020-11-30 14:09:10任务结束时间为:2020-11-30 14:09:17Trigger名称为:trigger1------------Trigger组名称为:group1数据库备份成功,当前时间为:2020-11-30 14:09:15
如果你要通过像日历那样按日程来触发任务,可以基于CronTrigger来进行任务调度。如每个工作日的上午9点,或者每隔一三五的上午9点-12点,诸如此类。
Cron表达式:
注意两点的是: 一、周中SAT表示的是7,SUN表示的是一。而非我们传统的周一到周日对应的1-7。 二、周和日字段不会同时存在,例如我们制定了日这个字段为每个月的15号,我们不能保证每个月的15号都是周一或者周二。同理,如果我们指定了每周一或者周二,日这个字段也不需要了,我们可以通过?来表示我不需要这个字段。 Cron特殊字符相关解释: 具体示例: 写在最后,当然我们也可以不用自己手写,我们可以直接通过网上的一些在线工具直接生成Cron表达式:
public class testScheduler implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String test = dateFormat.format(date); //工作内容 System.out.println("数据库备份成功,当前时间为:"+test); }}
public class testJobScheduler { public static void main(String[] args) throws SchedulerException, InterruptedException { //通过子类的方式获取对象 StdSchedulerFactory factory = new StdSchedulerFactory(); Scheduler scheduler = factory.getScheduler(); JobDetail jobDetail = JobBuilder .newJob(testScheduler.class) .withIdentity("job1", "group1") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .startNow()//马上启动触发器 .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?")) .build(); //scheduler关联任务实例和触发器 Date date = scheduler.scheduleJob(jobDetail, trigger); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("scheduler开始的时间是"+dateFormat.format(date)); //启动 scheduler.start(); //Scheduler执行两秒后挂起 Thread.sleep(2000L); //挂起,暂停任务 scheduler.standby(); //中止,杀死任务后不允许重启,有一个布尔类型的参数,true会等待所有任务执行完毕,而false则会直接关闭scheduler scheduler.shutdown(); //scheduler执行5秒后自动开启 Thread.sleep(5000L); scheduler.start(); }}
默认的Quartz.Properties,如果我们不想用这个文件,我们也可以在资源类目录下新建一个同名文件进行覆盖。
org.quartz.scheduler.instanceName: DefaultQuartzSchedulerorg.quartz.scheduler.rmi.export: falseorg.quartz.scheduler.rmi.proxy: falseorg.quartz.scheduler.wrapJobExecutionInUserTransaction: falseorg.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount: 10org.quartz.threadPool.threadPriority: 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: trueorg.quartz.jobStore.misfireThreshold: 60000org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
在任务调度中,监听你想关注的任务。主要的监听器有三种,分别为:
TriggerListener JobListener SchedulerListener 在这之前得先了解全局监听器和非全局监听器的区别: 全局监听器:能够接收所有Job/Listener的通知 非全局监听器:只能接收在其注册上的Job或者Trigger
JobListener的四种方法:
JobListener的使用方法:
第一步、编写监听类,实现JobListener接口,重写四种监听方法:
//实现JobListener接口public class MyJobListener implements JobListener { @Override public String getName() { String name = this.getClass().getName(); System.out.println("监听器的名称是:"+name); return name; } //前置方法 @Override public void jobToBeExecuted(JobExecutionContext context) { String name = context.getJobDetail().getKey().getName(); System.out.println("Job的名称是"+name+ "Scheduler在JobDetails即将执行时调用的方法"); } @Override public void jobExecutionVetoed(JobExecutionContext context) { String name = context.getJobDetail().getKey().getName(); System.out.println("job的名称是:"+name+"Scheduler在JobDetails即将执行时,但又被TriggerListener否决时调用的方法"); } //后置方法 @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { String name = context.getJobDetail().getKey().getName(); System.out.println("job的名称是:"+name+"Scheduler在JobDetails执行之后调用的方法"); }}
第二步、注册监听器即可
public class JobListenerScheduler { public static void main(String[] args) throws SchedulerException { //调度器(Scheduler),通过工厂模式从工厂中获取调度实例 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //任务实例(JobDetail) JobDetail jobDetail = JobBuilder .newJob(HelloJobListener.class) .withIdentity("job1", "group1") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .startNow()//马上启动触发器 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build(); //scheduler关联任务实例和触发器 scheduler.scheduleJob(jobDetail, trigger); //注册局部的JobListener,表示指定的任务job //scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1","group1"))); //注册全局的JobListener //scheduler.getListenerManager().addJobListener(new MyJobListener(),EverythingMatcher.allJobs()); //启动 scheduler.start(); }}
TriggerListener的五种方法:
public class MyTriggerListener implements TriggerListener { private String name; public MyTriggerListener(String name) { this.name = name; } @Override public String getName() { System.out.println("监听器的名称是:"+name); return name; } @Override public void triggerFired(Trigger trigger, JobExecutionContext context) { String name = trigger.getKey().getName(); System.out.println(name+"触发器被触发"); } @Override public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { String name = trigger.getKey().getName(); System.out.println(name+"触发器没被触发"); return true;//不会执行job方法,也就是说我的Job类中的数据库备份语句不会被输出 } @Override public void triggerMisfired(Trigger trigger) { String name = trigger.getKey().getName(); System.out.println(name+"错过触发"); } @Override public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) { String name = trigger.getKey().getName(); System.out.println(name+"完成之后触发"); }}
public class TriggerListenerScheduler { public static void main(String[] args) throws SchedulerException { //调度器(Scheduler),通过工厂模式从工厂中获取调度实例 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //任务实例(JobDetail) JobDetail jobDetail = JobBuilder .newJob(HelloJobListener.class) .withIdentity("job1", "group1") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .startNow()//马上启动触发器 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build(); //scheduler关联任务实例和触发器 scheduler.scheduleJob(jobDetail, trigger); //注册局部的JobListener,表示指定的任务job //scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simple"), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1","group1"))); //注册全局的TriggerListener //scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simple"), EverythingMatcher.allTriggers()); //启动 scheduler.start(); }}
与Scheduler有关的事件,增加删除一个Trigger/Job,scheduler发生错误,关闭scheduler等。
相关方法:
转载地址:http://ucqof.baihongyu.com/