(翻译)Quartz官方教程——第三课:关于任务和任务详情

正如我们在课程2中看到的那样,任务非常容易实现,因为Job接口中只有一个“execute”方法。但是你还需要了解更多任务的性质、Job接口的execute(...)方法和JobDetails类。

虽然你实现的任务类知道如何完成特定类型的任务,但是Quartz还需要知道你希望该作业的一个实例具有的各种属性。这是通过JobDetail类实现,它在前几课中有简单的提及过。

JobDetail实例是通过JobBuilder类构建的。为了在代码中拥有DSL的感觉,你可能想要使用静态导入它的所有方法:

import static org.quartz.JobBuilder.*;

现在让我们花点时间来谈一谈关于Jobs的“本质”和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时提供任务类class调度器就能知道运行时的任务类型。每当调度器执行任务的时候,在调用execute(...)方法之前它会创建一个新的任务类实例。当任务执行完毕,对作业类实例的引用被删除然后实例会被当做垃圾回收。这种行为的一个后果是作业必须有一个无参数的构造函数(当使用默认的JobFactory实现的时候)。另一个后果是,在任务类中定义状态数据字段是没有意义的——因为它们的值在作业执行之间不会被保留(译者著:即任务是无状态的)。

你现在可能想问“我如何提供Job实例的属性/配置?”以及“我如何跟踪执行之间的任务状态?”这些问题的答案只有一个:JobDataMap,JobDetail 对象的一部分。

JobDataMap

JobDataMap可以被用来保存任意数量的在任务运行中可用的(可序列化的)数据对象。JobDataMap是Java Map接口的一个实现,并且添加了一些方便的方法来保存和获取基本类型的数据。

以下是在将作业添加到调度程序之前,在定义/构建JobDetail时将数据放入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(将在教程的JobStore章节讨论)你需要稍稍考虑下放入JobDataMap的数据,因为其中的对象将被序列化,因此它们容易出现类版本问题。显然,标准的Java类型应该是非常安全的,但除此之外,任何时候有人改变你已经序列化实例的类的定义时,都必须注意不要打破兼容性。或者,您可以将JDBC-JobStore和JobDataMap规定为只允许存储基本类型和字符串,从而消除以后出现序列化问题的可能性。

如果你在任务类中添加了setter方法,并且成员变量名与JobDataMap中的键的名称相对应(例如下面示例中的数据的setJobSays(String val)方法),则Quartz的默认JobFactory实现会在初始化任务时自动调用这些setter方法给成员变量赋值,因此你就不需要在execute方法中显示的从map中获取这些值。

触发器同样拥有一个关联的JobDataMaps。如果你有一个任务可以通过多个触发器定期/重复的调用,并且对于每个触发器任务拥有不同的输入参数,它将派上用场。

JobExecutionContext维护了一个JobDataMap,用于在任务运行时方便的获取。它是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()方法中的代码更清晰。我们还可以认为,尽管代码比较长,但是如果程序员的IDE被用来自动生成setter方法,而不是手工编写从JobDataMap检索值的调用,那么代码的编码就更少了。如何选择取决于你自己。

任务 “实例”

许多使用者花了很多时间仍对什么构成了“任务实例”感到困惑。我们将尝试在这里和下面的部分中解释清楚关于任务状态和并发性的内容。

你可以创建一个单独的作业类,并通过创建JobDetails的多个实例(每个实例都具有自己的一组属性和JobDataMap)并将其全部添加到调度程序中,从而将其中的许多“实例定义”存储在调度程序中。

举个栗子,你可以创建一个类叫SalesReportJob来实现Job接口。这个任务通过获取到的参数(通过JobDataMap)来指定销售报告应该基于的销售人员的姓名。它们可能会创建多个任务定义(JobDetails),比如“SalesReportForJoe”和“SalesReportForMike”,它们分别在各自的JobDataMaps使用“joe”和“mike”作为输入。

当一个触发器触发时,相关联的JobDetail(实例定义)被加载,同时它引用的任务类通过Scheduler配置的JobFactory实例化。默认的JobFactory只调用任务类的newInstance()方法,然后尝试调用匹配到JobDataMap中key的setter方法。你可能想要实现自己的JobFactory来做一些事情,比如让应用程序的IoC或DI容器产生/初始化作业实例。

在Quartz术语中,我们将每个存储的JobDetail称为“任务定义”或“JobDetail实例”,并将每个正在执行的作业称为“任务实例”或“任务定义实例”。通常,如果我们只使用“job”这个词,我们就是指一个命名定义,或者JobDetail。当我们指的是实现Job接口的类时,我们通常使用术语“任务类”。

Job State and Concurrency

现在,是一些关于作业状态数据(又名JobDataMap)和并发性的附加说明。有几个注解可以添加到您的任务类中,从而影响Quartz在这些方面的行为。

@DisallowConcurrentExecution 是可以添加到任务类的注解,它告诉Quartz不要同时执行给定任务定义(指给定任务类)的多个实例。

注意那里的措词,因为它是非常仔细地选择的。在上一节的示例中,如果“SalesReportJob”具有此注解,那么在给定时间只能执行一个“SalesReportForJoe”实例,但它可以与“SalesReportForMike”实例同时执行。约束基于实例定义(JobDetail),而不是任务类的实例。虽然如此,(在Quartz的设计过程中)仍然决定在类本身上进行注释,因为它通常会影响类的编码方式。

@PersistJobDataAfterExecution 是可以添加到任务类中的注解,它告诉Quartz在execute()方法成功完成后(不抛出异常)更新JobDetail的JobDataMap的存储副本,使得相同作业(JobDetail)的下一次执行接收更新后的值而不是最初存储的值。像@DisallowConcurrentExecution 注解一样,它是用于任务定义实例,而不是一个任务类实例,虽然它被设计为在任务类上使用因为它通常会影响类的编码方式(例如'执行方法'中的代码需要'明确''有状态')。

如果你使用了 @PersistJobDataAfterExecution 注解,你应该强烈考虑也使用@DisallowConcurrentExecution 注解,以避免同时执行同一作业的两个实例(JobDetail)时可能会混淆(竞态条件)哪些数据被存储。

Other Attributes Of Jobs

下是可以通过JobDetail对象为作业实例定义的其他属性的简要摘要:

  • 持久化 —— 如果一个任务是非持久化的,当它不再有任何可用的触发器时会被调度器自动删除。换句话说,非持久化的任务的生命周期由触发器的存在限制。
  • RequestsRecovery —— 如果一个任务“requests recovery”并且它在运行时调度器被强制关闭(即进程在运行中崩溃或者服务器关机),那么它在调度器再次启动时会重新执行。此时,JobExecutionContext.isRecovering()方法会返回true

JobExecutionException

最后,我们需要告诉你有关Job.execute(....)方法的一些细节。唯一可以从execute方法抛出的异常(包括RuntimeExceptions)是JobExecutionException。因此,你通常应该用“try-catch”块来包装执行方法的全部内容。您还应该花一些时间查看JobExecutionException的文档,因为你的任务可以通过它为调度器提供关于如何处理异常的各种指令。

猜你喜欢

转载自my.oschina.net/icebergxty/blog/1797949