zoukankan      html  css  js  c++  java
  • Quartz学习笔记

    什么是job schedule system?


        job schedule system是负责在提前定义的时间运行或者通知某个应用组件的系统。举个样例来说。比方在每周一早上930发送email通知客户最新的业务情况。

    java.util.Timerjava.util.TimerTask
        TimerTimerTask是能够完毕job schedule的两个jdk提供的类,只是这不能称为一个systemTimerTimerTask是非常easy的,不直接支持持久化任务,线程池和类似日历(calendar-like)的计划安排,在完毕一些高级功能上开发者要进行大量的扩展。

    Quartz的简介
        Quartzopensymphony组织专攻job scheduling领域又一个开源利器,能够到http://www.opensymphony.com/quartz查看具体信息。Quartz是轻量级的组件,开发者仅仅须要载入单独的jar包就能够利用Quartz强大的日程安排功能。当然,假如你为Quartz配备了数据库持久化任务的特性,Quartz也能够非常好的利用这一点。从而在机器重新启动后还能够记住你原先安排的计划。

        Quartz中我们接触最多的接口使Scheduler接口,该接口的提供了计划安排的功能。比方schedule/unschedule计划、start/pause/stop Scheduler.

        Quartz提供一些经常使用的Listener(JobListener,TriggerListener,SchedulerListener)用于全然的监视计划安排和运行情况。

    開始我们的Quartz之旅

    ·  HelloWorld example:

    想必大家非常想看一个HelloWorld的样例了吧。那么还是以HellowWorld開始。

    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!");
         }

         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关联一个新的Triggerjob能够继续运行。

     眼下的Quartz实现中。存在两种Trigger,SimpleTriggerCronTrigger,SimpleTrigger用来完毕一些比方固定时间运行的任务。比方:从如今開始1分钟后等等;而CronTrigger(没错,和unixcron进程的含意一样)用来运行calendar-like的任务。比方:每周五下午300,每月最后一天等等。

     Trigger trigger = new SimpleTrigger("SayHelloWorldJobTrigger",
                     Scheduler.DEFAULT_GROUP,
                     new Date(),
                     null,
                     0,
                     0L); 这个构造方法中,第一个是Trigger的名称。第二个是Trigger的组名,第三个是任务開始时间。第四个是结束时间,第五个是反复
     次数(使用SimpleTrigger.REPEAT_INDEFINITELY常量表示无限次),最后一个是反复周期(单位是毫秒),那么这样就创建
     了一个立马并仅仅运行一次的任务。

     scheduler.scheduleJob(jobDetail, trigger);
     这条语句就是把jobTrigger关联,这样当Trigger觉得应该触发的时候就会调用(实际上是Scheduler调用)job.execute方法了。

     scheduler.start();
     千万别忘了加上上面的语句,这条语句通知Quartz使安排的计划生效。

     关于execute方法的參数JobExecutionContext
     JobExecutionContext就和非常多Context结尾的类功能一样。提供的运行时刻的上下文环境。JobExecutionContext中有
     Scheduler,JobDetail,Trigger等非常多对象的引用,从而当你在execute方法内部须须要这些对象的时刻提供的便利。

     JobDetailTriggernamegroup Scheduler实例相应了非常多jobtrigger的实例,为了方便的区分,Quartz使用namegroup这两个特性,正如你想向的一样。
     同一个group下不能有两个同样nameJobDetailTrigger同理
     同一个Scheduler下不能有两个同样groupJobDetail,Trigger同理
     JobDetailTrigger的全然限定名为:group + name

    ·  更深入的思考...


    HelloWorld的样例还不足以说明一些问题,一些人可能会这样问:假如execute方法中须要一些额外的数据怎么办?比方说execute
     中希望发送一封邮件。可是我须要知道邮件的发送者、接收者等信息?

     存在两种解决方式:
     1.JobDataMap类:
       每一个JobDetail都关联了一个JobDataMap实例,JobDataMapjava.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放入QuartzSchedulerContext中,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 andCronTriggerSimpleTrigger主要提供了跟 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

     

     

     

     

     

     

     

     

  • 相关阅读:
    GridView 几个受保护的方法的注释
    完全理解 IDisposable 接口的实现
    C++ 函数调用约定和名称修饰
    硬盘格式转换不影响数据_ convert命令FAT32转NTFS
    Win7系统修复_修复光盘的制作与使用
    双网卡共享上网设置
    Windows?XP系统修复方法
    无线路由器与有线路由器的连接(两个路由器连接)
    无线路由器“无线漫游”
    登录路由器没有弹出登录框_路由器无法登录解决办法
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8779693.html
Copyright © 2011-2022 走看看