Quartz是Java裡流行的一種開源任務調度框架。Quartz可以用來創建簡單或為運行十個,百個,甚至是好幾萬個Jobs這樣簡單復雜的日程表。Jobs可以做成標准的Java組件或 EJBs.本文會先大概介紹一下如何使用Quartz,然後重點是介紹實際項目裡,通過二次開發,增加任務調度的可管理性和異常處理,使它具備一定的商業任務調度框架的功能
Quartz要求一個任務必須實現接口Job的execute方法,如下一個簡單的Job:
import Java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class SimpleJob implements Job {
public SimpleJob() {
}
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
Thread.sleep(1000*20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Quartz將任務和時間觸發分開,因此,你還需要指定時間觸發,通常采用Cron方式,如每天早上六點,也可以指定某個固定時間,如2008年8月8號等。
如以下即指定每天早上六點
CronTrigger cronTrigger = new CronTrigger("triggerName", "triggerGroup");
try {
CronExpression cexp = new CronExpression("0 6 * * * ");
cronTrigger.setCronExpression(cexp);
} catch (Exception e) {
e.printStackTrace();
}
Scheduler 類負責將時間觸發指定給JobDetail,簡單的來說JobDetail封裝了你的任務,並可以提供任務名,所屬組,以及附加的一些參數,代碼如下:
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
JobDetail job = new JobDetail("jobName", "groupName", SimpleJob.class);
Scheduler.scheduleJob(job, cronTrigger);
Job在被觸發的時候,會通過反射實例化SimpleJob.class(因此你的Job必須有一個無參數的構造函數),並調用execute方法。
對於上列的SimpleJob,可以從execute方法輸入參數context裡獲取一些屬性,如任務名(如例子裡的jobName),所在組(如:groupName).更重要的是,context裡可以包含你指定的參數,如我們想讓SimpleJob在運行的時候休眠時間為50秒,也可以這麼寫:
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
int sleep = context.getJobDetail().getJobDataMap().getInt("sleep");
Thread.sleep(1000*sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
參數Sleep將由調度框架傳入,如下
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
JobDetail job = new JobDetail("job1", "group1", SimpleJob.class);
job.getJobDataMap().put("sleep", 50);
Scheduler.scheduleJob(job, trigger);
對於實際任務調度來說,Quartz只是提供了基本功能,擺在我們面前的仍然有一些需求Quartz並沒有內置。如
任務狀態管理:需要查看當前有哪些任務在運行,歷史上任務執行情況
異常處理:任務出現異常需要告警,或者手工強制執行。
任務依賴關系:任務A執行前,任務B必須執行成功。
本文的下半部分將介紹如何實現這些功能,這也是我要寫的重點.
首先,我們使用Annotation定義任務,如下一個任務
public class SimpleJob{
@Run
public void doit()
{
try {
Thread.sleep(1000*20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
也可以增加Stop,Pause,Resume等Annotation,在此略過
Annoatoin定義如下
import Java.lang.annotation.Documented;
import Java.lang.annotation.ElementType;
import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;
import Java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Run {
}
我傾向於用Annotation而不是接口是因為Annotation更靈活。比如一些傳統的任務調度程序入口就是static main(String[] args)方法,只需要用Annotation指示一下,而且,Annoation擴展性也好,如給一個任務命名,可以擴展Annoatoin實現,如下;
@Run(name="com.simpleJob")
public void doit(String[] args)
{
try {
Thread.sleep(1000*20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
用Annoaton定義任務後,這任務如何加入到Quartz框架裡?可以定義個新的Wrapper類,叫著JobWrapper,它是一個標准的Quartz的任務,即實現了接口Job ,當Quartz調用次類的時候,此類會根據DataMap裡的BatchDescription描述來調用正確的任務。
BatchDescription很簡單,有三個屬性
private String className;
private String cron;
private String[] paramenters=null;
className,即為使用Annotation定義的任務。
cron即為Cron時間表達式
paramenters 為任務的參數.
JobWrapper 是系統的核心,Quartz調用它,它轉而調用JobDescription描述的任務,多了這一層,就能很好的管理Job狀態,做一些異常處理等更為復雜的任務管理了。代碼如下:
public JobWrapper()
{
// be used to persist to database or other features
id = System.currentTimeMillis()+"_"+Thread.currentThread().getId();
stateChanged(id,JobStatus.INIT);
JobManager.instance().reg(this)
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
JobDataMap data = context.getJobDetail().getJobDataMap();
desc = (BatchDescription)data.get("JobData");
runParameters = desc.getParamenter();
try {
realJob = Class.forName(desc.getClassName()).newInstance();
} catch (Exception e1) {
e1.printStackTrace();
return ;
}
//look for the method with annotation Run
runMethod = getRunMethod();
//reg it ,then can get it later
try {
stateChanged(id,JobStatus.RUNNING)
runMethod.invoke(realJob, runParameters);
stateChanged(id,JobStatus.RUNNING)
} catch (IllegalArgumentException e) {
//ignore
e.printStackTrace();
return ;
} catch (IllegalAccessException e) {
//ignore
e.printStackTrace();
return ;
} catch (InvocationTargetException e) {
Throwable ex = e.getTargetException();
// handle exception ,now just put this exception to some queue
stateChanged(id,JobStatus.EXCEPTOIN,ex.getMessage()) ;
return ;
}
}
private void stateChanged(String id,JobStatus,String msg){
//此方法可以用來存儲任務到數據庫,以供查看狀態,如:
JobDao.save(id,name,JobStatus,msg,new Date());
}
private Method getRunMethod()
{
// first look up the method with run annotation,if not find,check the main method
if(runMethod!=null){
return runMethod;
}
Method[] methods = realJob.getClass().getDeclaredMethods();
for(Method m:methods)
{
Annotation[] annos = m.getAnnotations();
if(annos!=null&&annos.length!=0)
{
for(Annotation anno:annos)
{
//System.out.println(anno.annotationType());
if(anno.annotationType()==com.joelli.Run.class)
{
return m;
}
}
}
}
// look for the method public static void main,let ignore it
return null;
}
最後,然我們看看Quartz如何調用此類
//定義一個任務,類為com.Javamonkey.SimpleJob,參數為Null
BatchDescription batchDesc= new BatchDescription("com.Javamonkey.SimpleJob","15 0/2 * * * ?",null);
JobDetail job1 = new JobDetail(batchDesc.getClassName()+ ".Job", "group", JobWrapper.class);
job1.getJobDataMap().put("JobData", batchDesc);
CronTrigger cronTrigger = new CronTrigger(batchDesc.getClassName()+ ".Trigger");
CronExpression cexp = new CronExpression("0 6 * * * ");
cronTrigger.setCronExpression(cexp);
Scheduler.scheduleJob(job1, cronTrigger);
如上代碼,Quartz在時間觸發後,會實例話JobWrapper.class,並調用Execute方法。JobWrapper會根據BatchDescription獲得真正要運行的任務並調用,同時,紀錄任務狀態以供管理。