【3】More About Jobs and Job Details

参考链接http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-03.html

看了前面的两篇,创建Jobs是非常简单的,只要implements Jobexecute() 方法即可。只需要理解Job的原理,Job interface 的execute()方法,还有JobDetail 类。

通过JobDetail类可以传递一些属性给你自己实现的Jobs instance, JobBuilder创建JobDetail instances ,通过下面的代码导入JobBuilderstatic methods:

import static org.quartz.JobBuilder.*;

调度器(scheduler)的生命周期是从其start到shutdown的一段时间,下面来看Jobs的本质以及quartz里的job instance 的生命周期。看代码:

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

实现了execute() 方法的HelloJob:

 public class HelloJob implements Job {

    public HelloJob() {
    }

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

上面的代码中,给了调度器一个scheduler一个JobDetail实例,在创建JobDetail类的时候提供了HelloJob.class 。scheduler在执行HelloJob的时候,会先创建一个HelloJob实例去执行execute()方法,当execute()方法执行完,对HellJob实例的引用也消失了,之后就等待垃圾回收机制去处理这个对象实例了。这样的后果是Jobs必须要由一个无参数的构造器(当使用默认的JobFactory去实现的时候);另外一个结果是在Job class里定义字段就没有意义了,因为在Job执行期间这些值不会被保存下来。

那怎样去给Job instance提供properties和configuration呢?
在Job执行期间,怎样去跟踪一个Job的state?

为了解决这些问题,JobDetail里有一个厉害角色: JobDataMap

JobDataMap

Job instances 在执行的时候可以获得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();

在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 ,在往JobDataMap里放数据的时候就要十分小心了,因为数据会被serialized,就会存在类的版本的问题。标准的Java类型是安全的,如果某人改了你已经系列化好的类,那应该做些其他操作去保证兼容性。另外,可以让JDBC-JobStoreJobDataMap 处于一个只能保存原生类型和字符串的模式,以预防序列化对象可能出现的类版本的问题。

如果在你实现的Job里有一个setter方法,和JobDataMap的键名对应起来。比如对应上面的代码,在Job实例类里增加一个setJobSays(String val) 方法,quartz默认的JobFactory实现会在Job被初始化的时候自动的去调用那些setters方法,这样就不用在Job实现类的execute 方法中通过JobDataMap 去获取值了,可看下面的例子加深理解。

Triggers也有和他们绑定在一起的JobDataMaps,这对于在scheduler里的一个Job被多个Trigger绑定,这些Trigger里有定期、或重复的去触发Job的,每一个独立的Trigger要为自己绑定的Job提供不同的数据输入。

可以从JobExecutionContext 获取到JobDataMap , 这个JobDataMap 是JobDetail和Trigger里的JobDataMap的并集,存在相同的键名的话,Trigger会覆盖JobDetail的。

下面来看一个在Job执行期间从JobExecutionContext 里获取 merged 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在实例化Job类时,将JobDataMap中的值给注入进我们的Job instances:

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();
      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方法要比先前更cleaner了,所有的setter方法都可以通过IDE自动生成,不用手动编写代码从JobDataMap 去获取值,直接在execute() 中访问字段、获取被注入进字段中的值。

Job “Instances”

许多人很困惑一个Job instance 由什么组成,下面的内容是关于job stateconcurrency 的。

假如有现在创建了一个Job实例,由这个Job实例创建多个JobDetail instances,每个JobDetail设置各自的属性以及JobDataMap, 然后把它们都扔到scheduler中。

举个栗子,创建一个job类去实现SalesReportJob , 通过JobDataMap传递参数给这个Job, 指定比如销售商的名字、需要分析的销售报表。如此一来,我们就要通过JobDetails 去定义多个Job, 比如SalesReportForJoeSalesReportForMike, 在它们各自的Job里通过JobDataMap传递joemike

当Trigger触发Job时,在scheduler容器里,JobDetail instances 将被加载,具体的Job类被配置在scheduler中的JobFactory给实例化。默认的JobFactory只是简单的调用Job类的newInstance() 去创建实例,然后尝试去调用setter方法(和JobDataMap里的键名的对应)。可以创建自己的JobFactory实现,通过在系统应用里面的IOC或DI容器去生产和实例化Job。

关于上文名词的澄清,对于保存在scheduler中的中JobDetail, 行文中的描述是”job definition” 或者 “JobDetail instance”。当提到可执行的job使用的是”job instance”或者”instance of a job definition”。 使用比较多的job,指的是name definition 或者“JobDetail”。实现了Job接口的类,通常使用job class 去描述。(这是官方文档的对于名词的解释,我这里文章的翻译可能有出入,大伙按着官网来理解就好)

Job State and Concurrency

开始叙述job的状态(aka JobDataMap)和并发了,可以在你的Job class上加一些注解去影响quartz的行为。

@DisallowConcurrentExecution

这个注解告诉quartz不要并发的去执行一个Job class的多个job 实例(quartz not to execute multiple instances of a given job definition (that refers to the given job class) concurrently)。

比如前面所说的SalesReportJob 类上有这个注解,那么只能是只有一个SalesReportJob 实例在特定的时间点执行, 不能是两个,但此时SalesReportForMike可以并发的去执行。这是基于JobDetail实例的去实现的,而不是job class 实例。

@PersistJobDataAfterExecution

这个注解加在job class上告诉quartz在成功执行execute()方法后(没有抛出异常),更新保存在JobDetail的JobDataMap里的备份数据,这样在下次执行同一个job(JobDetail)时接收的是上一个Job更新后的值,而不是先前保存的值。跟@DisallowConcurrentExecution一样,是对JobDetail起作用的,不是job class instance。即使这个注解是定义在job class上的,但对于在编写job class类的代码时意义还是蛮大的,对于job中execute() 能有一个更直观的理解。

当使用@PersistJobDataAfterExecution 这个注解的时候,同时也应该使用@DisallowConcurrentExecution 这个注解,避免同一个job(JobDetail)的多个instances在并发的执行时从JobDataMap里获取数据时出现不一致。

Other Attributes Of Jobs

其他的一些通过JobDetail对象定义的属性,

  • Durability

    如果一个Job不是持久化的,在没有活跃的Trigger和其绑定时,将会自动的从scheduler里被删除。换句话说,非持久化的Job的生命周期是依赖于Trigger的存在的。

  • RequestsRecovery

    如果一个job “requests recovery”, 当job在执行的时候,scheduler被hard shutdown了(运行过程中崩溃,或者机器被shut off了),而后在scheduler重新启动后,job会继续执行。这样的话, JobExecutionContext.isRecovering() 方法将会返回true。

JobExecutionException

来看Job.execute(..) 的一些细节吧,在execute()方法中会抛出JobExecutionException 这个异常。由此,使用try-catch将execute()方法里的代码 包裹,当出现异常后告诉scheduler如何进行后续处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值