1. 程式人生 > >Quartz之Job與JobDetail淺析

Quartz之Job與JobDetail淺析

本地 etx tex per iteye 任務 jobs down ext.get

Quartz可以用來做什麽?

Quartz是一個任務調度框架。比如你遇到這樣的問題

  • 想每月25號,信用卡自動還款
  • 想每年4月1日自己給當年暗戀女神發一封匿名賀卡
  • 想每隔1小時,備份一下自己的愛情動作片 學習筆記到雲盤

這些問題總結起來就是:在某一個有規律的時間點幹某件事。並且時間的觸發的條件可以非常復雜(比如每月最後一個工作日的17:50),復雜到需要一個專門的框架來幹這個事。 Quartz就是來幹這樣的事,你給它一個觸發條件的定義,它負責到了時間點,觸發相應的Job起來幹活。

廢話不多說,代碼杠杠的。。。

public static void main(String[] args) {
     
try { //創建scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //定義一個Trigger Trigger trigger =TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") //定義name/group .startNow()//一旦加入scheduler,立即生效 .withSchedule(SimpleScheduleBuilder.simpleSchedule() //使用SimpleTrigger
.withIntervalInSeconds(1) //每隔一秒執行一次 .repeatForever()) //一直執行 .build(); //定義一個JobDetail JobDetail job =JobBuilder.newJob(HelloQuartz.class) //定義Job類為HelloQuartz類,這是真正的執行邏輯所在 .withIdentity("job1", "group1") //定義name/group .usingJobData("name", "quartz") //定義屬性
.build(); //加入這個調度 scheduler.scheduleJob(job, trigger); //啟動之 scheduler.start(); //運行一段時間後關閉 Thread.sleep(10000); scheduler.shutdown(true); } catch (Exception e) { e.printStackTrace(); } }
HelloQuartz類 
public class HelloQuartz implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail detail = context.getJobDetail();
        String name = detail.getJobDataMap().getString("name");
        System.out.println("say hello to " + name + " at " + new Date());
    }
}

jar包:

技術分享

這個例子很好的覆蓋了Quartz最重要的3個基本要素:

  • Scheduler:調度器。所有的調度都是由它控制。
  • Trigger: 定義觸發的條件。例子中,它的類型是SimpleTrigger,每隔1秒中執行一次(什麽是SimpleTrigger下面會有詳述)。
  • JobDetail & Job: JobDetail 定義的是任務數據,而真正的執行邏輯是在Job中,例子中是HelloQuartz。 為什麽設計成JobDetail + Job,不直接使用Job?這是因為任務是有可能並發執行,如果Scheduler直接使用Job,就會存在對同一個Job實例並發訪問的問題。而JobDetail & Job 方式,sheduler每次執行,都會根據JobDetail創建一個新的Job實例,這樣就可以規避並發訪問的問題。

Scheduler

Scheduler就是Quartz的大腦,所有任務都是由它來設施。

Schduelr包含一個兩個重要組件: JobStore和ThreadPool。

JobStore是會來存儲運行時信息的,包括Trigger,Schduler,JobDetail,業務鎖等。它有多種實現RAMJob(內存實現),JobStoreTX(JDBC,事務由Quartz管理),JobStoreCMT(JDBC,使用容器事務),ClusteredJobStore(集群實現)、TerracottaJobStore(什麽是Terractta)

ThreadPool就是線程池,Quartz有自己的線程池實現。所有任務的都會由線程池執行。

SchedulerFactory

SchdulerFactory,顧名思義就是來用創建Schduler了,有兩個實現:DirectSchedulerFactory和 StdSchdulerFactory。前者可以用來在代碼裏定制你自己的Schduler參數。後者是直接讀取classpath下的quartz.properties(不存在就都使用默認值)配置來實例化Schduler。通常來講,我們使用StdSchdulerFactory也就足夠了。

SchdulerFactory本身是支持創建RMI stub的,可以用來管理遠程的Scheduler,功能與本地一樣,可以遠程提交個Job什麽的。

1.job

技術分享

實現類JobDetail

    JobDetail job = JobBuilder.newJob(RemindJob.class)
            .withIdentity("job1", "group1").build();//創建一個任務

    
     /**
      * 創建觸發器
      * 第一種方式  不太好
      */
    SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger", "myTriggerGroup").
    withSchedule(SimpleScheduleBuilder.simpleSchedule().
            withIntervalInSeconds(3).
            repeatForever()).
            startAt(new Date(System.currentTimeMillis()+1000)).build();
    
    
    /**
     * 創建觸發器
     * 第二種 方式  非常好
     * 可以 好用  2013年每月的第三個星期五上午10:30觸發  0 30 10 ? * 6#3 2013
     * 2016年每月的第一個星期四下午16:17觸發   0 17 16 ? * 5#1 2016
     * 每天15點到16點每5分鐘運行一次,此外,每天17點到18點每5分鐘運行一次  

     */
    /*CronTrigger trigger=TriggerBuilder.newTrigger() 
            .withIdentity("myTrigger", "myTriggerGroup")
            .withSchedule(CronScheduleBuilder.cronSchedule("0 18 16 ? * 5#1 2016")).build();*/
    
    SchedulerFactory sf=new StdSchedulerFactory();//創建調度者工廠
    Scheduler scheduler = sf.getScheduler();//創建一個調度者
    scheduler.scheduleJob(job,trigger);//註冊並進行調度
    scheduler.start();//啟動調度
    
    //Thread.sleep(millis)
    
    //scheduler.shutdown();//關閉調度
    
    
RemindJob 類的定義
 */
public class RemindJob implements Job {
private RemindService service=new RemindService();
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        service.printPlan("你好!");
        
        Date date=new Date();
        String time = date.toString();
        System.out.println(time+"job is starting");
    }

可以看到,我們傳給scheduler一個JobDetail實例,因為我們在創建JobDetail時,將要執行的job的類名傳給了JobDetail,所以scheduler就知道了要執行何種類型的job;每次當scheduler執行job時,在調用其execute(…)方法之前會創建該類的一個新的實例;執行完畢,對該實例的引用就被丟棄了,實例會被垃圾回收;這種執行策略帶來的一個後果是,job必須有一個無參的構造函數(當使用默認的JobFactory時);另一個後果是,在job類中,不應該定義有狀態的數據屬性,因為在job的多次執行中,這些屬性的值不會保留。

那麽如何給job實例增加屬性或配置呢?如何在job的多次執行中,跟蹤job的狀態呢?答案就是:JobDataMap,JobDetail對象的一部分。

JobDataMap

JobDataMap中可以包含不限量的(序列化的)數據對象,在job實例執行的時候,可以使用其中的數據;JobDataMap是Java Map接口的一個實現,額外增加了一些便於存取基本類型的數據的方法。

將job加入到scheduler之前,在構建JobDetail時,可以將數據放入JobDataMap,如下示例:

    JobDetail job=JobBuilder.newJob(RemindJob.class)
            .withIdentity("job1", "group1")
            .usingJobData("hello", "we are family")
            .build();

在job的執行過程中,可以從JobDataMap中取出數據,如下示例:

@Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        service.printPlan("你好!");
        JobKey key=context.getJobDetail().getKey();
        
        JobDataMap map = context.getJobDetail().getJobDataMap();
        String string = map.getString("hello");
        System.out.println(key+"==========="+string);
        
        Date date=new Date();
        String time = date.toString();
        System.out.println(time+"job is starting");
    }

如果你使用的是持久化的存儲機制(本教程的JobStore部分會講到),在決定JobDataMap中存放什麽數據的時候需要小心,因為JobDataMap中存儲的對象都會被序列化,因此很可能會導致類的版本不一致的問題;Java的標準類型都很安全,如果你已經有了一個類的序列化後的實例,某個時候,別人修改了該類的定義,此時你需要確保對類的修改沒有破壞兼容性;更多細節,參考現實中的序列化問題。另外,你也可以配置JDBC-JobStore和JobDataMap,使得map中僅允許存儲基本類型和String類型的數據,這樣可以避免後續的序列化問題。

如果你在job類中,為JobDataMap中存儲的數據的key增加set方法(如在上面示例中,增加setJobSays(String val)方法),那麽Quartz的默認JobFactory實現在job被實例化的時候會自動調用這些set方法,這樣你就不需要在execute()方法中顯式地從map中取數據了。

在Job執行時,JobExecutionContext中的JobDataMap為我們提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的並集,但是如果存在相同的數據,則後者會覆蓋前者的值。

下面的示例,在job執行時,從JobExecutionContext中獲取合並後的JobDataMap:

@Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        service.printPlan("你好!");
        JobKey key=context.getJobDetail().getKey();
        
    /*    JobDataMap map = context.getJobDetail().getJobDataMap();
        String string = map.getString("hello");
        System.out.println(key+"==========="+string);*/


         JobDataMap map = context.getMergedJobDataMap();
         String string = map.getString("hello");
         System.out.println(key+"---------------------    "+string);
         

Quartz之Job與JobDetail淺析