quartz是一个任务调度框架,优于java原生Timer框架。用来定时处理任务
下载
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.0</version>
</dependency>
注意使用想要的版本代替上述版本号
例子
定义一个job
package com.learn.frame.quartz;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements Job{
private String name; //SchedulerFactory自动注入
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail detail = context.getJobDetail();
//String name = detail.getJobDataMap().getString("name");
//打印当前时间
SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(name + " : Current time is :"+simpleDateFormat.format(new Date()));
System.out.println(Thread.currentThread().getName()); //观察发现默认开了10个线程去跑
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试
package com.learn.frame.quartz;
import java.util.Date;
import org.junit.Test;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.DateBuilder.newDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
public class QuartzTest {
/**
* 将调度Trigger和要调度的任务JobDetail分离
* Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以修改或者替换Trigger,而不用重新定义与之关联的Job。
* @throws SchedulerException
* @throws InterruptedException
*/
@Test
public void test() throws SchedulerException, InterruptedException{
//创建scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//定义一个Trigger
Trigger trigger = newTrigger().withIdentity("trigger1", "group1") //定义name/group
.startNow()//一旦加入scheduler,立即生效
.withSchedule(simpleSchedule() //使用SimpleTrigger
.withIntervalInSeconds(1) //每隔一秒执行一次
.repeatForever()) //一直执行,奔腾到老不停歇
.build();
//定义一个JobDetail
JobDetail job = newJob(HelloJob.class) //定义Job类为HelloQuartz类,这是真正的执行逻辑所在
.withIdentity("job1", "group1") //定义name/group
.usingJobData("name", "quartz") //定义属性
.build();
//加入这个调度
scheduler.scheduleJob(job, trigger);
//启动之
scheduler.start();
//运行一段时间后关闭
Thread.sleep(10000);
scheduler.shutdown(true);
}
}
结果
quartz : Current time is :2018-07-05 16:33:33
DefaultQuartzScheduler_Worker-1
quartz : Current time is :2018-07-05 16:33:34
DefaultQuartzScheduler_Worker-2
quartz : Current time is :2018-07-05 16:33:35
DefaultQuartzScheduler_Worker-3
quartz : Current time is :2018-07-05 16:33:36
DefaultQuartzScheduler_Worker-4
quartz : Current time is :2018-07-05 16:33:37
DefaultQuartzScheduler_Worker-5
quartz : Current time is :2018-07-05 16:33:38
DefaultQuartzScheduler_Worker-6
Quartz API
Quartz API的关键接口:
Job : 实现execute方法定义处理任务Job : 实现execute方法定义处理任务
JobDetail : 定义作业Job的实例,一个job 可以有多个JobDetail
Trigger : 触发器,定义指定Job的执行计划
Schedular : 调度器
Job接口
package org.quartz;
public interface Job {
public void execute(JobExecutionContext context)
throws JobExecutionException;
}
可以发现上述代码执行时,Schedular开了多个线程去执行job。而且在Job实现类中可以获得JobDetail中定义的JobDataMap数据。
原因:当Job的一个trigger被触发时,execute()方法由调度程序的一个工作线程调用。并传递给execute()方法一个Job执行的上下文对象 JobExecutionContext。
Job实例
很多用户对于Job实例到底由什么构成感到很迷惑。我们在这里解释一下,并在接下来的小节介绍job状态和并发。
你可以只创建一个job类,然后创建多个与该job关联的JobDetail实例,每一个实例都有自己的属性集和JobDataMap,最后,将所有的实例都加到scheduler中。
比如,你创建了一个实现Job接口的类“SalesReportJob”。该job需要一个参数(通过JobdataMap传入),表示负责该销售报告的销售员的名字。因此,你可以创建该job的多个实例(JobDetail),比如“SalesReportForJoe”、“SalesReportForMike”,将“joe”和“mike”作为JobDataMap的数据传给对应的job实例。
当一个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引用的job类通过配置在Scheduler上的JobFactory进行初始化。默认的JobFactory实现,仅仅是调用job类的newInstance()方法,然后尝试调用JobDataMap中的key的setter方法。你也可以创建自己的JobFactory实现,比如让你的IOC或DI容器可以创建/初始化job实例。
在Quartz的描述语言中,我们将保存后的JobDetail称为“job定义”或者“JobDetail实例”,将一个正在执行的job称为“job实例”或者“job定义的实例”。当我们使用“job”时,一般指代的是job定义,或者JobDetail;当我们提到实现Job接口的类时,通常使用“job类”。
Job状态与并发
Quartz 定时任务默认都是并发执行的(Schedular中维护着多个线程),不会等待上一次任务执行完毕,只要间隔时间到就会执行,如果定时任执行太长,会长时间占用资源(无法释放线程资源),导致其它任务堵塞。
如何让任务阻塞式执行? -Job实现类添加@PersistJobDataAfterExecution注解
详解:关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。
@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。拿前一小节的例子来说,如果“SalesReportJob”类上有该注解,则同一时刻仅允许执行一个“SalesReportForJoe”实例,但可以并发地执行“SalesReportForMike”类的一个实例。所以该限制是针对JobDetail的,而不是job类的。但是我们认为(在设计Quartz的时候)应该将该注解放在job类上,因为job类的改变经常会导致其行为发生变化。
@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。由job类来承载注解,是因为job类的内容经常会影响其行为状态(比如,job类的execute方法需要显式地“理解”其”状态“)。
如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。
@DisallowConcurrentExecution
public class HelloJob implements Job{
private String name; //SchedulerFactory自动注入
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail detail = context.getJobDetail();
//String name = detail.getJobDataMap().getString("name");
//打印当前时间
SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(name + " : Current time is :"+simpleDateFormat.format(new Date()));
System.out.println(Thread.currentThread().getName()); //观察发现默认开了10个线程去跑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
让当前线程睡眠2秒后,job并不是按照Trigger中定义,每一秒触发一次。线程会阻塞直到上个任务执行完成
JobDetail
为什么既有Job,又有Trigger呢?
在开发Quartz的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。
例如,Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以修改或者替换Trigger,而不用重新定义与之关联的Job。
key
将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其身份属性。Job和Trigger的key(JobKey和TriggerKey)可以用于将Job和Trigger放到不同的分组(group)里,然后基于分组进行操作。同一个分组下的Job或Trigger的名称必须唯一,即一个Job或Trigger的key由名称(name)和分组(group)组成。
JobDataMap
每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。
那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,将数据保存在JobDetail或Trigger中
*如果你在job类中,为JobDataMap中存储的数据的key增加set方法(如在上面示例中,增加setName()方法),那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样你就不需要在execute()方法中显式地从map中取数据了。
在Job执行时,JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
Job Stores
JobStore负责跟踪您提供给调度程序的所有“工作数据”:jobs,triggers,日历等
1)RAMJobStore
将所有数据保存在RAM中
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
2)JDBC JobStore
1 创建表
JDBCJobStore,必须首先创建一组数据库表以供Quartz使用
以2.3.x版本为例 quartz-quartz-2.3.0\quartz-core\src\main\resources\org\quartz\impl\jdbcjobstore 目录下包含不同数据库的建表sql,导入数据库生成下右图表
2.配置事务
创建表后,在配置和启动JDBCJobStore之前,您还有一个重要的决定。您需要确定应用程序需要哪种类型的事务。如果您不需要将调度命令(例如添加和删除triggers)绑定到其他事务,那么可以通过使用JobStoreTX作为JobStore 来管理事务(这是最常见的选择)。
如果您需要Quartz与其他事务(即J2EE应用程序服务器)一起工作,那么您应该使用JobStoreCMT - 在这种情况下,Quartz将让应用程序服务器容器管理事务。
3.配置数据源
最后一个难题是设置一个DataSource,JDBCJobStore可以从中获取与数据库的连接。DataSources在Quartz属性中使用几种不同的方法之一进行定义。一种方法是让Quartz创建和管理DataSource本身 - 通过提供数据库的所有连接信息。另一种方法是让Quartz使用由Quartz正在运行的应用程序服务器管理的DataSource,通过提供JDBCJobStore DataSource的JNDI名称。
quartz.properties (默认文件名)
#============================================================================
# Configure JobStore
#============================================================================
#default config
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#持久化配置
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
#我们仅为数据库制作了特定于数据库的代理
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
org.quartz.jobStore.useProperties:true
#数据库表前缀
org.quartz.jobStore.tablePrefix:qrtz_
org.quartz.jobStore.dataSource:qzDS
#============================================================================
# Configure Datasources
#============================================================================
#JDBC驱动
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.qzDS.user:root
org.quartz.dataSource.qzDS.password:123456
org.quartz.dataSource.qzDS.maxConnections:10
小结
在Quartz可以完成其工作之前需要配置的主要组件有:
- 线程池 ThreadPool
- JobStore
- DataSources(如有必要)
- 计划程序本身 Schedular实例