什麼是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 |