前一段时间项目需要做一个定时发送消息的功能,该功能依附于Web应用上,即当Web应用启动时,该应用就开始作用。起先决定使用java.util.Timerjava.util.TimerTask来实现,但是研究了一下以后发现Java Timer的功能比较弱,而且其线程的范围不受Web应用的约束。后来发现了Quartz这个开源的调度框架,非常有趣。

首先我们要得到Quartz的最新发布版。目前其最新的版本是1.6。我们可以从以下地址获得它的完整下载包,包中可谓汤料十足,不仅有我们要的quartz.jar,更包含多个例程和详细的文档,从API到配置文件的XSD一应俱全。感兴趣的朋友也可以在src目录下找到该项目的源码一看究竟。

废话少说,下面就来看一看这个东东是怎么在Java Web Application中得以使用的。

首先不得不提出的是Quartz的三个核心概念:调度器、触发器、作业。让我们来看看他们是如何工作的吧。

一.作业总指挥——调度器

1. Scheduler接口

该接口或许是整个Quartz中最最上层的东西了,它提携了所有触发器和作业,使它们协调工作。每个Scheduler都存有JobDetailTrigger的注册,一个Scheduler中可以注册多个JobDetail和多个Trigger,这些JobDetailTrigger都可以通过group name和他们自身的name加以区分,以保持这些JobDetailTrigger的实例在同一个Scheduler内不会冲突。所以,每个Scheduler中的JobDetail的组名是唯一的,本身的名字也是唯一的(就好像是一个JobDetailID)。Trigger也是如此。

Scheduler实例由SchedulerFactory产生,一旦Scheduler实例生成后,我们就可以通过生成它的工厂来找到该实例,获取它相关的属性。下面的代码为我们展示了如何从一个Servlet中找到SchedulerFactory并获得相应的Scheduler实例,通过该实例,我们可以获取当前作业中的testmode属性,来判断该作业是否工作于测试模式。

//从当前Servlet上下文中查找StdSchedulerFactory

            ServletContext ctx=request.getSession().getServletContext();

            StdSchedulerFactory factory = (StdSchedulerFactory) ctx.getAttribute("org.quartz.impl.StdSchedulerFactory.KEY");

           

            Scheduler sch = null;

            try {

                //获取调度器

                sch = factory.getScheduler("SchedulerName");

                //通过调度器实例获得JobDetail,注意领会JobDetailNameGroupName的用法

                JobDetail jd=sch.getJobDetail("JobDetailName", "GroupName");

                Map jobmap1=jd.getJobDataMap();

                istest=jobmap1.get("testmode")+"";

            } catch (Exception se) {

                //如果得不到当前作业,则从配置文件中读取testmode

                ReadXML("job.xml").get(“job.testmode”);

            }

 

Scheduler实例生成后,它处于"stand-by"模式,需要调用其start方法来使之投入运作。

public class SendMailShedule{

    //设置标准SchedulerFactory

    static SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

    static Scheduler sched;

   

    public static void run()throws Exception{

        //生成Scheduler实例

             sched = schedFact.getScheduler();

        //创建一个JobDetail实例,对应的Job实现类是SendMailJob

             JobDetail jobDetail = new JobDetail("myJob",sched.DEFAULT_GROUP,SendMailJob.class);

        //设置CronTrigger,利用Cron表达式设定触发时间

        CronTrigger trigger = new CronTrigger("myTrigger","test","0 0 8 1 * ?");

        sched.scheduleJob(jobDetail, trigger);

        sched.start();

    }

   

    public static void  stop()throws Exception{

        sched.shutdown();

    }

}

另外,我们也可以通过监听器来跟踪作业和触发器的工作状态。

二.作业及其相关

1. Job

作业实际上是一个接口,任何一个作业都可以写成一个实现该接口的类,并实现其中的execute()方法,来完成具体的作业任务。

2. JobDetail

JobDetail可以指定我们作业的详细信息,比如可以通过反射机制动态的加载某个作业的实例,可以指定某个作业在单个调度器内的作业组名称和具体的作业名称,可以指定具体的触发器。

一个作业实例可以对应多个触发器(也就是说学校每天10点放一次眼保健操录音,下午3点半可以再放一次),但是一个触发器只能对应一个作业实例(10点钟的时候学校不可能同时播放眼保健操和广播体操的录音)。

3. JobDataMap

这是一个给作业提供数据支持的数据结构,使用方法和java.util.Map一样,非常方便。当一个作业被分配给调度器时,JobDataMap实例就随之生成。

Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。

正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job

如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。

JobDataMap实例也可以与一个触发器相关联。这种情况下,对于同一作业的不同触发器,我们可以在JobDataMap中添加不同的数据,以便作业在不同时间执行时能够提供更为灵活的数据支持(学校上午放眼保健操录音第一版,下午放第二版)。

不管是有状态还是无状态的任务,在任务执行期间对TriggerJobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。

 下一篇 Quartz调度框架应用总结(续1)

文章来源:http://x-spirit.spaces.live.com/Blog/cns!CC0B04AE126337C0!550.entry