关于Quartz的一些概念介绍

来源:互联网 发布:java项目源代码网站 编辑:程序博客网 时间:2024/05/16 01:40

Job 非常容易实现,这个接口中只有一个 execute() 方法。Quartz 中可能需要为 Job 实例设置属性,这个功能通过 JobDetail 类来完成。

JobDetail 实例通过 JobBuilder 创建。你可以使用静态导入所有的方法,这样可以在你的代码中使用 DSL 风格:

import static org.quartz.JobBuilder.*;

让我们花点时间来讨论 Job 在 Quartz 中的生命周期:

// define the job and tie it to our HelloJob class  JobDetail job = newJob(HelloJob.class)      .withIdentity("myJob", "group1") // name "myJob", group "group1"      .build();          // Trigger the job to run now, and then every 40 seconds  Trigger trigger = newTrigger()      .withIdentity("myTrigger", "group1")      .startNow()      .withSchedule(simpleSchedule()          .withIntervalInSeconds(40)          .repeatForever())                  .build();          // Tell quartz to schedule the job using our trigger  sched.scheduleJob(job, trigger);

现在,假设 HelloJob 的定义如下:

public class HelloJob implements Job {    public HelloJob() {    }    public void execute(JobExecutionContext context)      throws JobExecutionException    {      System.err.println("Hello!  HelloJob is executing.");    }  }

注意,我们给了调度器一个 JobDetail 实例,JobDetail 中提供了 Job 的 class 对象,因此它知道调用的 Job 类型。每次调度器执行 Job,它会在调用 execute(…) 方法前创建一个新的 Job 实例。当执行完成后,所有 Job 的引用将会丢弃,这些对象会被垃圾回收。 基于前面的描述,首先 Job 类需要一个无参构造方法,另外,在 Job 中存储状态属性是没有意义的,因为每次执行完成后,对象将会被删除。

你可能想问“我如何为 Job 实例提供配置属性”或者“我怎么保存 Job 的状态数据”。这些问题的关键是 JobDataMap 对象,它是 JobDetail 对象中的一部分。

JobDataMap

JobDataMap 可以用来保存数据对象(序列化)。JobDataMap 其实是 Java Map 接口的一个实现,并且添加了一些方便的方法用于存储和获取原始数据类型。

下面的例子将存储数据到 JobDataMap :

// define the job and tie it to our DumbJob class  JobDetail job = newJob(DumbJob.class)      .withIdentity("myJob", "group1") // name "myJob", group "group1"      .usingJobData("jobSays", "Hello World!")      .usingJobData("myFloatValue", 3.141f)      .build();

下面的例子演示如何在执行期间从 JobDataMap 获取数据:

public class DumbJob implements Job {    public DumbJob() {    }    public void execute(JobExecutionContext context)      throws JobExecutionException    {      JobKey key = context.getJobDetail().getKey();      JobDataMap dataMap = context.getJobDetail().getJobDataMap();      String jobSays = dataMap.getString("jobSays");      float myFloatValue = dataMap.getFloat("myFloatValue");      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);    }  }

如果你使用 JobStore 存储,那么你需要小心决定在 JobDataMap 中存放什么数据,因为对象将会序列化,因此会有一些 class 类型的问题。标准的 Java 类都非常安全,但是如果你要使用自己定义的类,那么任何时候你要改变类定义,都要小心不要破坏兼容性。你可以只保存 String 和原始数据类型从而消除可能发生的序列化问题。

如果你添加了 set 方法到你的 Job 类中,并且和 JobDataMap 中存放的键一致(例如,上面例子中添加 setJobSays(String val) 方法),然后 Quartz 默认的 JobFactory 实现将会自动在 Job 实例化的时候调用这些 set 方法。

Trigger 也可以关联 JobDataMap。这可用于当你需要在多个 Trigger 中使用相同的 Job 的时候,为每个 Job 设置不同的输入数据。JobDataMap 可以在 Job 执行期间从 JobExecutionContext 中获得。它将会合并 JobDetail 和 Trigger 中的 JobDataMap,如果名称相同,那么后者的值将会覆盖前者的值。下面的例子将演示如何从 JobExecutionContext 中获取 JobDataMap:

public class DumbJob implements Job {    public DumbJob() {    }    public void execute(JobExecutionContext context)      throws JobExecutionException    {      JobKey key = context.getJobDetail().getKey();      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example      String jobSays = dataMap.getString("jobSays");      float myFloatValue = dataMap.getFloat("myFloatValue");      ArrayList state = (ArrayList)dataMap.get("myStateData");      state.add(new Date());      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);    }  }

或者,如果你想要依赖 JobFactory 注入映射值到你的类中,那么可以使用下面的代码:

public class DumbJob implements Job {    String jobSays;    float myFloatValue;    ArrayList state;          public DumbJob() {    }    public void execute(JobExecutionContext context)      throws JobExecutionException    {      JobKey key = context.getJobDetail().getKey();      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example      state.add(new Date());      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);    }        public void setJobSays(String jobSays) {      this.jobSays = jobSays;    }        public void setMyFloatValue(float myFloatValue) {      myFloatValue = myFloatValue;    }        public void setState(ArrayList state) {      state = state;    }      }

你可能已经注意到代码有点长,但是 execute() 方法更加清晰整洁。无论使用哪种方式,都取决于你自己的决定。

Job 实例

你可以创建一个 Job 类,然后通过创建多个 JobDetail 实例与 Job 关联,并保存到调度器中(每个任务都有自己的属性和 JobDataMap),这个 JobDetail 称为 Job 实例。

例如,你可以创建一个实现了 Job 接口的类,命名为“SalesReportJob”。这个类可以接收一个参数(通过 JobDataMap)用于定义销售报表基于哪个销售人员。它们可以创建多个 Job 实例(使用 JobDetail),例如 “SalesReportForJoe” 和 “SalesReportForMike”,这里使用了由 JobDataMap 传入 “joe” 和 “mike” 作为参数。

当 Trigger 被触发,关联的 JobDetail 将会被加载,并且 Job 类会通过 JobFactory 配置到 Scheduler。默认的 JobFactory 将会简单地调用 Job Class 的 newInstance() 方法,并尝试调用 set 方法将 JobDataMap 中同名的属性设置到 Job 中。

Job 状态和并发

有一组可添加到 Job 的 Annotation,可以影响 Quartz 的行为。

@DisallowConcurrentExecution 添加到 Job 类后,Quartz 将不会同时执行多个 Job 实例(什么是 Job 实例可参看上一节)。
注意措辞。我们用上一节的例子来讲解,如果 “SalesReportJob” 上添加了这个 Annotation,那么同时只能执行一个“SalesReportForJoe”,但是却可以同时执行“SalesReportForMike”。因此,可以说这个约束是基于 JobDetail 的而不是基于 Job 的。

@PersistJobDataAfterExecution 添加到 Job 类后,表示 Quartz 将会在成功执行 execute() 方法后(没有抛出异常)更新 JobDetail 的 JobDataMap,下一次执行相同的任务(JobDetail)将会得到更新后的值,而不是原始的值。就像@DisallowConcurrentExecution 一样,这个注释基于 JobDetail 而不是 Job 类的实例。

如果你使用了 @PersistJobDataAfterExecution 注释,那么强烈建议你使用 @DisallowConcurrentExecution 注释,这是为了避免出现并发问题,当多个 Job 实例同时执行的时候,到底使用了哪个数据将变得很混乱。

Job 的其它属性

下面列举了一些通过 JobDetail 定义的 Job 属性:

  • Durability – 持久性,如果 Job 是非持久性的,那么执行完 Job 后,如果没有任何活动的 Trigger 与之关联,那么将会被调度器自动删除。换句话说,非持久性的 Job 的生命周期与它关联的 Trigger 相关。
  • RequestsRecovery – 如果任务设置了 RequestsRecovery,那么它在调度器发生硬停止(例如,当前进程 crash,或者机器宕机)后,当调度器再次启动的时候将会重新执行。这种情况下,JobExecutionContext.isRecovering() 方法将会返回 true。

JobExecutionException

最后,我们来看看 Job.execute(…) 方法。这个方法只允许抛出一种异常(包括 RuntimeException),那就是 JobExecutionException。正是因为如此,你通常需要将 execute() 方法中的所有内容放入 try-catch 语句块中。你也需要花点时间看看 JobExecutionException 的文档,你的任务可以使用它提供的各种指令来控制如何处理异常。