Quartz教程三:Job与JobDetail介绍

原文链接 | 译文链接 | 翻译:nkcoder | 校对:

本系列教程由quartz-2.2.x官方文档翻译、整理而来,希望给同样对quartz感兴趣的朋友一些参考和帮助,有任何不当或错误之处,欢迎指正;有兴趣研究源码的同学,可以参考我对quartz-core源码的注释(进行中)。

正如在教程二中讲到的,Job实现起来很容易,该接口只有一个“execute”方法。本节主要关注:Job的特点、Job接口的execute方法以及JobDetail。

你定义了一个实现Job接口的类,这个类仅仅表明该job需要完成什么类型的任务,除此之外,Quartz还需要知道该Job实例所包含的属性;这将由JobDetail类来完成。

JobDetail实例是通过JobBuilder类创建的,导入该类下的所有静态方法,会让你编码时有DSL的感觉:

    import static org.quartz.JobBuilder.*;

让我们先看看Job的特征(nature)以及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”类可以如下定义:

    public class HelloJob implements Job {

        public HelloJob() {
        }

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

可以看到,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。

那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分。

JobDataMap

JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。

将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap,如下示例:

    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中存放什么数据的时候需要小心,因为JobDataMap中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题;Java的标准类型都很安全,如果你已经有了一个类的序列化后的实例,某个时候,别人修改了该类的定义,此时你需要确保对类的修改没有破坏兼容性;更多细节,参考现实中的序列化问题。另外,你也可以配置JDBC-JobStore和JobDataMap,使得map中仅允许存储基本类型和String类型的数据,这样可以避免后续的序列化问题。

如果你在job类中,为JobDataMap中存储的数据的key增加set方法(如在上面示例中,增加setJobSays(String val)方法),那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样你就不需要在execute()方法中显式地从map中取数据了。

在Job执行时,JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的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实现数据的自动“注入”,则示例代码为:

    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中获取数据了,所以你实际需要编写的代码更少了。当前,如何选择,由你决定。

Job实例

很多用户对于Job实例到底由什么构成感到很迷惑。我们在这里解释一下,并在接下来的小节介绍job状态和并发。

你可以只创建一个job类,然后创建多个与该job关联的JobDetail实例,每一个实例都有自己的属性集和JobDataMap,最后,将所有的实例都加到scheduler中。

比如,你创建了一个实现Job接口的类“SalesReportJob”。该job需要一个参数(通过JobdataMap传入),表示负责该销售报告的销售员的名字。因此,你可以创建该job的多个实例(JobDetail),比如“SalesReportForJoe”、“SalesReportForMike”,将“joe”和“mike”作为JobDataMap的数据传给对应的job实例。

当一个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引用的job类通过配置在Scheduler上的JobFactory进行初始化。默认的JobFactory实现,仅仅是调用job类的newInstance()方法,然后尝试调用JobDataMap中的key的setter方法。你也可以创建自己的JobFactory实现,比如让你的IOC或DI容器可以创建/初始化job实例。

在Quartz的描述语言中,我们将保存后的JobDetail称为“job定义”或者“JobDetail实例”,将一个正在执行的job称为“job实例”或者“job定义的实例”。当我们使用“job”时,一般指代的是job定义,或者JobDetail;当我们提到实现Job接口的类时,通常使用“job类”。

Job状态与并发

关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。

@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。拿前一小节的例子来说,如果“SalesReportJob”类上有该注解,则同一时刻仅允许执行一个“SalesReportForJoe”实例,但可以并发地执行“SalesReportForMike”类的一个实例。所以该限制是针对JobDetail的,而不是job类的。但是我们认为(在设计Quartz的时候)应该将该注解放在job类上,因为job类的改变经常会导致其行为发生变化。

@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。由job类来承载注解,是因为job类的内容经常会影响其行为状态(比如,job类的execute方法需要显式地“理解”其”状态“)。

如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。

Job的其它特性

通过JobDetail对象,可以给job实例配置的其它属性有:

  • Durability:如果一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除。也就是说,非持久的job的生命期是由trigger的存在与否决定的;
  • RequestsRecovery:如果一个job是可恢复的,并且在其执行的时候,scheduler发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当scheduler重新启动的时候,该job会被重新执行。此时,该job的JobExecutionContext.isRecovering() 返回true。

JobExecutionException

最后,是关于Job.execute(..)方法的一些额外细节。execute方法中仅允许抛出一种类型的异常(包括RuntimeExceptions),即JobExecutionException。因此,你应该将execute方法中的所有内容都放到一个”try-catch”块中。你也应该花点时间看看JobExecutionException的文档,因为你的job可以使用该异常告诉scheduler,你希望如何来处理发生的异常。

时间: 2024-09-08 12:52:04

Quartz教程三:Job与JobDetail介绍的相关文章

ASP.NET 5系列教程 (三):view components介绍

在ASP.NET MVC 6中,view components (VCs) 功能类似于虚拟视图,但是功能更加强大. VCs兼顾了视图和控制器的优点,你可以把VCs 看作一个Mini 控制器.它负责控制应用中的某一功能模块,例如: 动态导航菜单 标签云 登录面板 购物车 最近文章 博客侧边栏 假如使用VC 创建了登录面板,可以在很多场景中调用,例如: 用户没有登录 用户已登录,需要退出使用其他帐号登录或者管理其他帐号. 如果当前登录角色为管理员,渲染管理员登录面板 你可以根据用户的需求获取数据进行

Bootstrap零基础入门教程(三)_javascript技巧

什么是 Bootstrap? Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的. 历史 Bootstrap 是由 Twitter 的 Mark Otto 和 Jacob Thornton 开发的.Bootstrap 是 2011 年八月在 GitHub 上发布的开源产品. 写到这里,这篇从零开始学Bootstrap(3)我想写以下几个内容: 1. 基于我对Bootstrap的理解,做一个小小的总结.

C#微信公众号开发系列教程三(消息体签名及加解密)

原文:C#微信公众号开发系列教程三(消息体签名及加解密)   C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南)    距离上一篇博文已经半个月了,本来打算每两天更新一次的,但可怜苦逼码农无日无夜的加班.第一篇博文发表后,博文视点的编辑就找到我,问我想不想出版这个系列,我当时瞬间就想到了王大锤的独白,想想真的是有点小激动,后面按照那边的要求,提交了申请书,也提交了目录,可惜文笔不行,再加上最近太

黄聪:Microsoft Enterprise Library 5.0 系列教程(三) Validation Application Block (初级)

原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(三) Validation Application Block (初级) 企业库提供了一个很强大的验证应用程序模块,特点是: 可以通过配置为你的程序中特定的类来定义规则集. 是为你的类的公有属性,即对外开放的属性进行验证的. 使用企业库验证应用程序模块的优势: 有助于保持一致的验证方法. 包括大多数标准验证,包括.NET数据类型校验. 它让您可以将多个规则集具有相同的类和该类的成员. 它可以让你申请一个或多个

MVC5 + EF6 完整入门教程三

原文:MVC5 + EF6 完整入门教程三 期待已久的EF终于来了. 学完本篇文章,你将会掌握基于EF数据模型的完整开发流程. 本次将会完成EF数据模型的搭建和使用. 基于这个模型,将之前的示例添加数据库查询验证功能. 文章提纲 概述 & 要点 详细步骤 总结 概述 & 要点 下面是本文要点,正文部分会有详细介绍. EF架构图 新建基于EF的Data Model的约定 关于ORM的重要概念,和传统方式开发的区别 EF开发的整体过程 详细步骤 新建文件夹,规划好代码摆放位置 创建相关类 (D

MyBatis学习教程(三)-MyBatis配置优化_java

一.连接数据库的配置单独放在一个properties文件中 之前,我们是直接将数据库的连接配置信息写在了MyBatis的conf.xml文件中,如下: <?xml version="." encoding="UTF-"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config .//EN" "http://mybatis.org/dtd/mybatis--

微信开放平台 公众号第三方平台开发 教程三 一键登录授权给第三方平台

原文:微信开放平台 公众号第三方平台开发 教程三 一键登录授权给第三方平台 教程导航: 微信开放平台 公众号第三方平台开发 教程一 平台介绍 微信开放平台 公众号第三方平台开发 教程二 创建公众号第三方平台 微信开放平台 公众号第三方平台开发 教程三 一键登录授权给第三方平台  微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo   公众号第三方平台的开放,是为了让公众号运营者,在面向垂直行业需求时,可以一键登录授权给第三方的公众号运营平台,通过第三方开发者提供的公众

Dreamweaver 4 简明教程(三、制作前的准备工作——定义网站)

dreamweaver|教程 三.制作前的准备工作--定义网站 在正式开始制作网页之前,最好先定义一个新网站,这是为了更好地利用站点窗口对站点文件进行管理. 定义的方法 方法一:在站点窗口的下拉菜单选择最后一项Define Sites,如下图. 此时会弹出一个新的对话框,点击New按钮是定义一个新网站,如果日后需要对旧有网站重新定义,可以在左边的网站列表中选中你需要重新定义的网站,然后点击 Edit 按钮便可. 方法二:选择命令菜单 Site > New Site 选项. 基本设置 在接着看到的

PEAR教程(二)--Pear的Cache介绍

前面介绍了pear的安装,今天开始介绍pear的几个知名的package之一,如果有不清楚的地方请站内搜索"PEAR教程"获取前面的教程! 今天我们要介绍的是PEAR的Cache_Lite包,做web的说到提速眼睛瞪大的程度绝不亚于男人见到绝色美女时眼睛所瞪大的程度,因此,我这里第一个要介绍的就是PEAR的Cache_lite包,利用这个package可以根据你的需要缓存网页的任何一个部分,从而大大的提高了页面的生成和载入速度! 首先去Pear的List Packages下载Cache