Quartz2.2.x官方文档2.2.X—第三章 3.关于Jobs和Job Details

Quartz 教程


Table of Contents | ‹ Lesson 2 | Lesson 4 ›

课程3: 关于 Jobs 和 Job Details

正如你课程2看到的,Jobs十分容易实现,只需要实现接口中的‘execute’ 方法。你还需要了解更多关于jobs的性质,Job接口的execute(..)方法,关于JobDetails。

当你实现job类通过代码知道怎么实现不同类型的Job,Quartz需要配置你希望job拥有的不同属性。这是通过JobDetail类完成的,在上一节简要提到过。

JobDetail实例的构建使用JobBuilder类。你通常可以使用静态导入所有方法,这样可以使你的代码拥有DSL的感觉。


import static org.quartz.JobBuilder.*;

现在让我们讨论一下关于Jobs的'本质'和在Quartz中的Job实例生命周期的问题。首先让门看一些在我们第一课中看过得代码片段:


  // 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' Job类定义如下:


  public class HelloJob implements Job {

    public HelloJob() {
    }

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

我们给这个scheduler一个JobDetail实例,它知道这个被执行job的类型,你只需简单提供job类就可以构造JobDetail。每次调度执行这个job, 都会在调用它的execute(..)方法前,创建一个这个类的新实例。当执行完成后,对job类实例的引用将删除,实例会被垃圾收集器回收。这种行为使得Jobs必须有一个无参的构造函数(当使用默认JobFactory实现时). 另一方面在job类中定义数据字段是没有意义的 - 因为在job执行期间这些值不会被保存.

你可能会问“那我怎样能赋予Job实体属性/配置呢? 和 “我怎样能在job执行期间追踪它的状态?” 这些问题的答案是同一个:关键在于JobDataMap, 它是JobDetail对象的一部分.

JobDataMap

JobDataMap能够用来保存你希望在Job执行时能够传递给Job的任意数量的数据。JobDataMap是Java Map接口的一种是实现, 并且有一些遍历的方法,来存储和检索原始类型的数据。

下面是当定义/构建JobDetail时一些往JobDataMap中放入数据的片段,job添加至调度程序之前:


  // 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();

这儿有一个当job执行期间,从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(在本教程的JobStore部分讨论) 你应该在决定使用JobDataMap的地方小心谨慎,因为它会将对象序列化,而这容易导致类的版本问题。显然标准Java类型是十分安全的,但是对已经序列化的实体修改它们的定义,特别要注意不要破坏它们的兼容性。当然, 你可以只将原始类型放入JDBC-JobStore和JobDataMap,只有字符串被允许存放到map中,这样就排除了任何序列化问题的可能。

如果你给job类添加set方法,这个方法的名称相当于在JobDataMap中的key。(例如上个例子中的数据,setJobSays(String val)方法), 接着当job实例化后,Quartz’s默认的JobFactory实现将会自动调用这些set方法,这样就避免了在执行方法中显示的获取map中的值.

Triggers也可以有与之关联的JobDataMaps。在你有一个Job在调度程序储存中,并且有多个触发器定期/重复使用的情况下是十分有用的,每一个触发器触发时,你都可以给Job提供不同的数据输入。

在Job执行期间通过JobExecutionContext获得JobDataMap是十分方便的。它的是JobDatail的JobDataMap和Trigger中JobDataMap值的合并,后者中任何与前者中相同的值,都会把前者覆盖。

这里有一个例子,关于在Job执行期间从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向你的类注入map值,它可能是这样的:


  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()方法的代码更加清晰。虽然这个代码更长,它实际上编写代码更少,如果你的IDE开发工具使用了set方法自动生成,那么就不需要单独再从JobDataMap中获取数据。至于如何选择在于你了。

Job “Instances”

许多用户对于花费时间教你如何构建"job 实例"感到困惑。我们将会在下部分讲解关于job状态和并发的事情。

你可以创建一个单独的job类,然偶胡在调度程序中创建需要JobDetails的实例 - 每个都有自己的属性和JobDataMap - 并将它们全部添加到调度程序中.

例如,你可以创建一个实现Job接口的类"SalesReportJob"。这个job被用来根据输入给它的参数(通过JobDataMap),来指定销售报告根据销售员来生成。他们可以创建多个job的定义(JobDetails), 例如“SalesReportForJoe” 和“SalesReportForMike” ,在JobDataMaps中将"joe"和"mike"分别输入给对应的jobs。

当一个触发触发时,JobDetail(实例定义) 与加载有关,它所引用的job类是通过在调度程序中的JobFactory配置初始化的。默认的JobFactory会简单的调用job class的newInstance()方法,然后尝试在类中调用JobDataMap中与key名称相匹配的set方法。你可能希望创建你自己的JobFactory让你应用程序的Ioc或则DI容器生产/初始化job实例。

在“Quartz speak”, 我们将每一个存储的JobDetail作为"job definition"或者“JobDetail instance”, 我们将每一个调用的job称为"job实例"或者"job定义的实例"。通常如果我们只是使用"job",我们使用的是"job"这个词,一般我们指"job"这个词的意思是名称定义或者JobDetail。当我们提到job接口的实现,我们通常使用"job class"。

Job 状态和并发

现在, 需要注意job的状态数据(又称作JobDataMap)和并发. 有一些注解被添加到你的Job类中,这些注解会影响在这些方面Quartz的行为。

@DisallowConcurrentExecution 是可以添加到Job类的注释,告诉Quartz不要同时执行给定job定义的多个实例(指的是给定的做作业类) .
注意这里的描述,小心的选择使用。在前一节例子中,如果“SalesReportJob”上有这个注解,在给定时间内只有一个"SalesReportForJoe"实例被执行, 但是能够同时执行多个"SalesReportForMike"实例。约束基于实例定义(JobDetail),不是工作类的实例。不管怎样, 它(在Quartz定义中)都明确的指出注释应加载类本身,因为它通常会对类的编码产生影响。

@PersistJobDataAfterExecution 是一个能够在execute()方法完全执行后,告诉Quartz更新JobDetail的JobDataMap的存储副本的注解。这样同一个Job(JobDetail)下一次执行将会收到更新后的值而不是原始存储的值。就像@DisallowConcurrentExecution 注解, 这适用于作业定义实例,而不是一个作业类实例,尽管决定让job类携带属性,因为会对类的编码产生影响。(例如在执行方法中,"statefulness"需要显示指定"understood"。

如果你适用@PersistJobDataAfterExecution注解, 你也应该好好思考需要使用@DisallowConcurrentExecution 注解, 因为为了避免同一个Job的两个实例同时执行时,数据可能会混乱的问题。

其他Jobs属性

下面是能够通过JobDetail对象定义一个job实例的其他属性快速总结:

  • Durability - 如果job非持久化,一旦job与任何活跃触发器失去关联则自动从调度程序中删除。换句话说,非持久jobs有一个 生命周期,trigger存在是必须的。
  • RequestsRecovery - 如果一个任务在"请求中恢复",它在调度程序硬关闭期间执行(例如在运行中崩溃,或者机器被关闭) , 接着当调度程序开始运行它会重新被执行。此时,JobExecutionContext.isRecovering()方法会返回true。

JobExecutionException

最后,我们需要告诉你一些Job.execute(..)方法的一些详细信息。你可以从execute方法抛出唯一异常(包含运行时异常)是JobExecutionException。因此,你应该在使用execute方法的地方用'try-catch'包裹起来。你可以用些时间来看下JobExecutionException的文档,你可以通过它知道如何处理这个异常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章