什么是job schedule system?html
job schedule system是负责在提早定义的时间运行或者通知某个应用组件的系统。举个样例来讲。比方在每周一早上9:30发送email通知客户最新的业务状况。java
java.util.Timer和java.util.TimerTask
Timer和TimerTask是可以完毕job schedule的两个jdk提供的类,只是这不能称为一个system。Timer和TimerTask是很是easy的,不直接支持持久化任务,线程池和相似日历(calendar-like)的计划安排,在完毕一些高级功能上开发者要进行大量的扩展。web
Quartz的简介
Quartz是opensymphony组织专攻job scheduling领域又一个开源利器,可以到http://www.opensymphony.com/quartz查看具体信息。Quartz是轻量级的组件,开发者仅仅需要载入单独的jar包就可以利用Quartz强大的日程安排功能。固然,假如你为Quartz配备了数据库持久化任务的特性,Quartz也可以很是好的利用这一点。从而在机器从新启动后还可以记住你原先安排的计划。sql
Quartz中咱们接触最多的接口使Scheduler接口,该接口的提供了计划安排的功能。比方schedule/unschedule计划、start/pause/stop Scheduler.数据库
Quartz提供一些常用的Listener(JobListener,TriggerListener,SchedulerListener)用于全然的监视计划安排和运行状况。post
開始咱们的Quartz之旅spa
· HelloWorld example:线程
想必你们很是想看一个HelloWorld的样例了吧。那么仍是以HellowWorld開始。unix
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
/**
* @author snowway
* @version $Id$
*/
public class SayHelloWorldJob implements Job{
/*
* (non-Javadoc)
*
* @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
*/ public void execute(JobExecutionContext context) throws JobExecutionException{
System.out.println("hello world!");
}postgresql
public static void main(String[] args) throws Exception{
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
JobDetail jobDetail = new JobDetail("SayHelloWorldJob",
Scheduler.DEFAULT_GROUP,
SayHelloWorldJob.class);
Trigger trigger = new SimpleTrigger("SayHelloWorldJobTrigger",
Scheduler.DEFAULT_GROUP,
new Date(),
null,
0,
0L);
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
为了简单起见,我把main方法写在SayHelloWorldJob中了,运行SayHelloWorldJob可以看到控制台打印hello world.
· 回想Hello World example:
Job是什么?
接口Job是每个业务上需要运行的任务需要实现的接口,该接口仅仅有一个方法:
package org.quartz;
public interface Job {
public void execute(JobExecutionContext context)
throws JobExecutionException;
}
execute方法也就是当时间到达后。Quartz回调的方法,咱们使SayHelloWorldJob实现Job接口以提供打印功能
JobDetail是什么? JobDetail描写叙述了一个任务具体的信息,比方名称,组名等等。
JobDetail jobDetail = new JobDetail("SayHelloWorldJob",
Scheduler.DEFAULT_GROUP,
SayHelloWorldJob.class);
在上面的构造方法中。第一个是任务的名称,第二个是组名,第三个就是实际当任务需要运行的回调类。
Trigger是什么?
Trigger顾名思义就是触发器。Quartz有个很是好的想法就是分离了任务和任务运行的条件。
Trigger就是控制任务运行条件的类。当Trigger以为运行条件知足的时刻。Trigger会通知相关的Job去运行。分离的优势是:
1.你可以为某个Job关联多个Trigger,当中不论什么一个条件知足都可以触发job运行,这样可以完毕一些组合的高级触发条件
2.当Trigger失效后(比方:一个永远都不能知足的条件),你没必要去声明一个新的job。取代的是你可以为job关联一个新的Trigger让job可以继续运行。
眼下的Quartz实现中。存在两种Trigger,SimpleTrigger和CronTrigger,SimpleTrigger用来完毕一些比方固定时间运行的任务。比方:从现在開始1分钟后等等;而CronTrigger(没错,和unix的cron进程的含意同样)用来运行calendar-like的任务。比方:每周五下午3:00,每个月最后一天等等。
Trigger trigger = new SimpleTrigger("SayHelloWorldJobTrigger",
Scheduler.DEFAULT_GROUP,
new Date(),
null,
0,
0L); 这个构造方法中,第一个是Trigger的名称。第二个是Trigger的组名,第三个是任务開始时间。第四个是结束时间,第五个是反复
次数(使用SimpleTrigger.REPEAT_INDEFINITELY常量表示无限次),最后一个是反复周期(单位是毫秒),那么这样就建立
了一个立马并仅仅运行一次的任务。
scheduler.scheduleJob(jobDetail, trigger);
这条语句就是把job和Trigger关联,这样当Trigger以为应该触发的时候就会调用(其实是Scheduler调用)job.execute方法了。
scheduler.start();
千万别忘了加上上面的语句,这条语句通知Quartz使安排的计划生效。
关于execute方法的參数JobExecutionContext
JobExecutionContext就和很是多Context结尾的类功能同样。提供的运行时刻的上下文环境。JobExecutionContext中有
Scheduler,JobDetail,Trigger等很是多对象的引用,从而当你在execute方法内部须需要这些对象的时刻提供的便利。
JobDetail和Trigger的name和group Scheduler实例相应了很是多job和trigger的实例,为了方便的区分,Quartz使用name和group这两个特性,正如你想向的同样。
同一个group下不能有两个一样name的JobDetail。Trigger同理
同一个Scheduler下不能有两个一样group的JobDetail,Trigger同理
JobDetail和Trigger的全然限定名为:group + name
· 更深刻的思考...
HelloWorld的样例还不足以说明一些问题,一些人可能会这样问:假如execute方法中需要一些额外的数据怎么办?比方说execute
中但愿发送一封邮件。但是我需要知道邮件的发送者、接收者等信息?
存在两种解决方式:
1.JobDataMap类:
每个JobDetail都关联了一个JobDataMap实例,JobDataMap是java.util.Map的子类,基本上是提供key-value形式的数据。并提供了一些便利方法(主要是对java基本数据类型的支持,如put(String key,int value))。当开发者建立JobDetail的时候。可以把附加信息放到JobDataMap中。那么在execute方法中可以依据key找到需要的值。
JobDetail job = new JobDetail....
job.getJobDataMap().put("from","snowway@vip.sina.com");
...
在execute中
String from = jobExecutionContext.getJobDetail().getJobDataMap().getString("from");
....
只是,当你使用数据库存储JobDetail的时候(默认状况下使用RAM),这里有一个致命的弱点。你不能把没有实现java.io.Serializable的对象放入JobDataMap中。因为Quartz将使用Blob字段保存(也可以经过配置文件关闭)序列化过的JobDataMap中的对象。比方你在execute方法中需要一个java.sql.Connection接口实例。这样的状况也是广泛的,那么一般状况下你不能把Connection放入JobDataMap。即便你仅仅想在execute中使用。
(注:读者可临时以为上面这段话是正确的。然而可以经过指示quartz改变这样的行为。那属于高级话题)
2.假如你需要一个java.sql.Connection,用于在execute中完毕某些操做。那么你可以把Connection放入Quartz的SchedulerContext中,execute也可以訪问,并且Quartz不会持久化SchedulerContext中的不论什么东西。
scheduler.getContext().put("java.sql.Connection",connection);
execute中
Connection con = (Connection)jobExecutionContext.getScheduler().getContext().get("java.sql.Connection");
Java 中已经有一个 timer 类可以用来进行运行计划。定时任务。咱们所要作的仅仅是 继承 java.util.TimerTask 类。例如如下所看到的:
package com.yourcompany.scheduling;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class ReportGenerator extends TimerTask {
public void run() {
System.out.println("Generating report");
//TODO generate report
}
}
class MainApplication {
public static void main(String[] args) {
Timer timer new Timer();
Calendar date = Calendar.getInstance();
date.set(
Calendar.DAY_OF_WEEK,
Calendar.SUNDAY
);
date.set(Calendar.HOUR, 0);
date.set(Calendar.MINUTE, 0);
date.set(Calendar.SECOND, 0);
date.set(Calendar.MILLISECOND, 0);
// Schedule to run every Sunday in midnight
timer.schedule(
new ReportGenerator(), // TimerTask
date.getTime(), // Timer
1000 * 60 * 60 * 24 * 7 // delay
);
}
}
这里有几个问题。咱们的类继承了TimerTask ,而timerTask 也是实现了 java.lang.Runnable 接口。
咱们所要作的仅仅是在咱们本身的类里重置 run() 方法。因此咱们的TimerTask类事实上是一种线程,但线程的调度每每不是依照咱们但愿来实现的,因为一些垃圾收集等缘由,咱们计划的时间点。却没有运行必要的任务。
这样会产生一些问题。尽管,Timer 类也提供了scheduleAtFixedRate() 方法用来在垃圾收集后可以高速的追上任务进度,但这个不必定是咱们所需要的。特别是在 一些 J2EE server上 Timer 是没法控制的,因为它不在容器的权责范围内。
另外的。这个任务调度也缺少一些企业级所需要的 特殊 日期定制的功能,以及改动,查找任务的功能。
这里咱们要介绍的是一个开源项目:Quartz 。
Quartz 定义了两种 基本接口 Job 和 Trigger 。 看名字也就知道,咱们的任务必须实现 Job, 咱们的时间触发器定义在 Trigger 内。
看一个样例或许能更快的了解他的用法: package com.yourcompany.scheduling;
import org.quartz.*;
public class QuartzReport implements Job {
public void execute(JobExecutionContext cntxt) //必须实现的方法
throws JobExecutionException {
System.out.println(
"Generating report - " +
cntxt.getJobDetail().getJobDataMap().get("type")
);
//TODO Generate report
}
public static void main(String[] args) {
try {
SchedulerFactory schedFact
new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
JobDetail jobDetail =
new JobDetail(
"Income Report", // 任务名
"Report Generation", // 任务组
QuartzReport.class //任务运行的类
);
jobDetail.getJobDataMap().put(
"type",
"FULL"
);
CronTrigger trigger new CronTrigger(
"Income Report", //触发器名
"Report Generation" //触发器组
);
trigger.setCronExpression( // 触发器时间设定
"0 0 12 ? * SUN"
);
sched.scheduleJob(jobDetail, trigger); // 运行任务
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里面咱们可以看到。当咱们定义了任务运行 QuartzReport 类后。需要定一个Scheduler类用来运行计划任务。
一个JobDetail 类来描写叙述这个任务的信息,包含任务信息,任务所在组。任务运行的类。
而后还要定义一个 触发器。相似的也包含触发器名,触发器所在组,触发器触发时间设定。
最后是调度器Scheduler类运行计划任务。
基本上一个计划任务运行的流程就完毕了。
固然。咱们还看到了上面红色表明的内容。这些内容主要是提供在job方法运行的时候所需要的參数的提供。这里使用了JobDataMap 类,它事实上就是实现了map的特殊应用的一个类。用法与Map 很是相似。咱们可以用 put() 输入參数。在Job类中使用cntxt.getJobDetail().getJobDataMap().get("type") 方法获取输入的參数的值。这里的cntxt 是JobExecutionContext 。
就是包含任务运行上下文的一个信息类。这样咱们的一个主要的任务运行就可以搞定了。
触发器有两类:SimpleTrigger andCronTrigger. 。SimpleTrigger主要提供了跟 java.util.Timer 类相似的功能.。
你可以在里面定义 任务的起始时间,终止时间,任务的运行次数,任务运行的中间间隔 。
而 CronTrigger类主要提供了更高级的任务调度时间设置,好比 每个星期天的早上7点 。CronTrigger的时间设置说明在最后来介绍。
如下咱们介绍一下在 J2EE 环境下怎样来使用 Quartz 。
首先。咱们要配置 web.xml ,加入 一下内容。主要是Quartz 的初始化,
<servlet>
<servlet-name>QuartzInitializer</servlet-name>
<display-name>Quartz Initializer Servlet</display-name>
<servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
而后还要有一个Quartz 的配置文件 quartz.properties 放置在 WEB-INF/classes文件夹如下。
StdScheduleFactory()会读取它。配置例如如下 #
# Configure Main Scheduler Properties
#
org.quartz.scheduler.instanceName = TestScheduler
org.quartz.scheduler.instanceId = one
#
# Configure ThreadPool
#
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 4
#
# Configure JobStore
#
org.quartz.jobStore.misfireThreshold = 5000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
这里咱们使用的 RAMJobStore 存储方式。这样假设咱们的webserver从新启动的话,咱们所有未运行的任务信息都回丢失。固然,咱们也有另外的选择,咱们可以把这样的信息存储在数据库内,就是使用JDBCJobStoreTX
#
# Configure ThreadPool
#
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
#
# Configure Datasources
#
org.quartz.dataSource.myDS.driver = org.postgresql.Driver
org.quartz.dataSource.myDS.URL = jdbc:postgresql:dev
org.quartz.dataSource.myDS.user = dejanb
org.quartz.dataSource.myDS.password =
org.quartz.dataSource.myDS.maxConnections 5
附:cronExpression配置说明
字段 |
|
赞成值 |
|
赞成的特殊字符 |
秒 |
|
0-59 |
|
, - * / |
分 |
|
0-59 |
|
, - * / |
小时 |
|
0-23 |
|
, - * / |
日期 |
|
1-31 |
|
, - * ? / L W C |
月份 |
|
1-12 或者 JAN-DEC |
|
, - * / |
星期 |
|
1-7 或者 SUN-SAT |
|
, - * ? / L C # |
年(可选) |
|
留空, 1970-2099 |
|
, - * / |
Cron 的小小说明
表示方式 |
意义 |
"0 0 12 * * ?" |
Fire at 12pm (noon) every day |
"0 15 10 ? * *" |
Fire at 10:15am every day |
"0 15 10 * * ? " |
Fire at 10:15am every day |
"0 15 10 * * ? *" |
Fire at 10:15am every day |
"0 15 10 * * ? 2005" |
Fire at 10:15am every day during the year 2005 |
"0 * 14 * * ?" |
Fire every minute starting at 2pm and ending at 2:59pm, every day |
"0 0/5 14 * * ?" |
Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day |
"0 0/5 14,18 * * ?" |
Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day |
"0 0-5 14 * * ?" |
Fire every minute starting at 2pm and ending at 2:05pm, every day |
"0 10,44 14 ? 3 WED" |
Fire at 2:10pm and at 2:44pm every Wednesday in the month of March. |
"0 15 10 ? * MON-FRI" |
Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday |
"0 15 10 15 * ? " |
Fire at 10:15am on the 15th day of every month |
"0 15 10 L * ? " |
Fire at 10:15am on the last day of every month |
"0 15 10 ? * 6L" |
Fire at 10:15am on the last Friday of every month |
"0 15 10 ? * 6L" |
Fire at 10:15am on the last Friday of every month |
"0 15 10 ? * 6L 2002-2005" |
Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005 |
"0 15 10 ? * 6#3" |
Fire at 10:15am on the third Friday of every month |