四、Quartz中Job相关类的理解和使用

(一)、Job

  Job可以理解为就是一个工作任务,代码中就是一个实现了org.quartz.Job或org.quartz.StatefulJob接口的java类。当Scheduler决定运行Job时,execute()方法就会被执行。
  具体可以干啥:
   1、每天定时发送系统邮件
   2、在指定的时刻发送一条短信给用户
   3、执行完A任务后希望B任务在10秒后执行
   4、。。。
  总结就是任何java能做的任务都可以成为一个job。

  注意:在 @Quartz集成-项目使用中已经讲到,org.quartz.Job接口是一个无状态的任务接口,实现此接口的任务每当触发器触发时都会准时执行,调度器并不关心此任务是否有其他实例正在运行,如果任务存在阻塞,可能会导致大量的任务实例并行执行。如果业务中存在不允许并行执行的任务,此时就用到有状态的org.quartz.StatefulJob接口,此接口的任务实现类不能并行执行,必须等上一次触发器触发执行完成后 才可以进行下一次触发执行。
  那么Job和StatefulJob在代码上具体有什么区别呢?
  它们没有区别,只是名字不同而已,StatefulJob接口继承了Job接口,框架只是通过接口的名字来标识是无状态任务还是有状态任务。

(二)、JobDetail

  JobDetail(org.quartz.JobDetail)从字面的意思理解就是任务详情,它包含了任务本身和任务的其他信息。从 @Quartz集成-项目使用中的代码可以看出,是将一个JobDetail实例通过scheduleJob()方法注册给了调度器,而不是一个Job实现类的实例。注册在调度器上的每一个Job都只会创建一个JobDetail实例,而当真正执行JobDetail中的Job时,调度器才会对此Job实现类进行实例化,并运行它的execute()方法。这样的好处就是我们无需担心线程安全性问题,因为相当于同一时刻仅有一个线程去执行Job实例。

JobDetail的属性介绍:

属性名 详细介绍
name 任务的名称,框架需要通过此名称进行关联,所以需要保证唯一性
group 任务组,默认为“DEFAULT”,取值Scheduler.DEFAULT_GROUP
jobClass Job的实现类类对象,注意不能使用代理类,原因下面介绍
description 任务描述信息,可以用来传递字符串参数,但是尽量不要这么干
jobDataMap 类似Map类,可以通过此类的对象向Job的execute方法中传递业务参数
volatility 重启应用之后是否删除任务的相关信息,默认为false
durability Job执行完后是否继续持久化到数据库,默认为false
shouldRecover 服务器重启后是否忽略过期的任务,默认为false

JobDetail类中主要的几个方法:

构造方法:
public JobDetail(){}
public JobDetail(String name, Class jobClass){}  // 常用
public JobDetail(String name, String group, Class jobClass){} // 常用
public JobDetail(String name, String group, Class jobClass, boolean volatility, boolean durability, boolean recover){}

常用方法:
public String getName(){}      // 获取Job的名称
public JobDataMap getJobDataMap(){}   //获取Job中需要使用到的数据

(三)、JobDataMap

  实际开发中的任务业务代码不是简单的打印几句话而已,经常会需要传入各种参数才能完成。我们可以通过哪些办法传递参数到Job的execute()方法中呢?
(1)、通过JobDetail中的jobDataMap属性
  JobDataMap最顶层就是实现了java.util.Map接口,所以将它当做Map来使用就可以。但是需要注意的是,org.quartz.jobStore.userProperties默认配置为false,设置为true时表示JobDataMap中的value存放的类型必须是String类型,此配置有利于持久化时不会出现多个类版本。

  存放非字符串时会抛出一个异常信息:org.quartz.JobPersistenceException: Couldn't store job: JobDataMap values must be Strings when the 'useProperties' property is set.

  解决办法:
   1、在quartz.properties配置文件中加入org.quartz.jobStore.userProperties=false或直接删除此条配置取默认值
   2、将对象转JSON字符串后存入JobDataMap。

(2)、在execute方法中根据Job的name属性(名称是唯一的)在数据库中查询
  数据库表记录如果生成的是UUID,那么可以将Job的类名+UUID作为Job的名称,比如:
TimingSendMessage_4a6c346ea44d4515946b87099275446a,TimingSendMessage是Job实现类的名称,4a6c346ea44d4515946b87099275446a是业务记录的唯一标识,然后在execute()方法中通过JobDetail实例的getName()方法获取到名称,再通过“_”下划线切割,这样就能拿到UUID了,最后通过此UUID在数据库中查询需要的数据。此方法的缺点就是代码看上去很不优雅而且代码理解稍加复杂。

(3)、使用JobDetail的description属性传递简单的String类型参数
  直接调用setDescription(String description)方法,然后在execute()方法中通过get方法获取。虽然这样做可以达到目的,但是最好不要这样,因为你写的代码可能最后只有你自己懂或自己都忘了干了什么。

(四)、开发中的一些小技巧和注意事项

(1)、Job的execute()方法中需要使用到Spring管理的依赖bean
  Spring帮忙管理bean的好处就是自己不用new对象啦,但是前面的文章提到过,Job的实例是在真正调度JobDetail中的Job实现类时进行实例化的,此对象由框架本身进行管理,所以实例化的对象并没有交给Spring来管理,所以通过@Autowired注入时,会有点麻烦。

  • 方法1、通过实现了org.springframework.context.ApplicationContextAware接口的java类来获取,具体实现代码这里不讲述了。获取示例:
ITaskStoreService taskStoreService = SpringContextHolder.getBean("taskStoreService");
  • 通过@Autowired注解注入
package com.quartz.task;

import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.xk.quartz.entity.TaskStore;
import com.xk.quartz.service.impl.ITaskStoreService;

// 注意加上注解让Spring扫描到此Job类
@Component
public class DefaultJob implements Job{

    // 需要注入的依赖bean
    private static ITaskStoreService taskStoreService;

    // 定义一个set方法
    @Autowired
    public void setTaskStoreService(ITaskStoreService taskStoreService) {
        DefaultJob.taskStoreService = taskStoreService;
    }
        
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();
        String name = jobDetail.getName();
        // 使用依赖的bean
        TaskStore store = taskStoreService.selectById(name);
        System.out.println(store);
    }
}

(2)、Job的execute()方法不会被Spring进行事务管理,如何保证一致性?
用上面一段代码稍作修改做以下示例:

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
      JobDetail jobDetail = context.getJobDetail();
      String name = jobDetail.getName();
      // 查询记录
      TaskStore store = taskStoreService.selectById(name);
      // 更新记录
      store.setUpdateDate(new Date());
      taskStoreService.updateById(store);
      // 删除记录
      taskStoreService.deleteById("表记录的id");
}

  从代码可以看出,execute()方法既进行了更新和删除,如果删除方法因为网络等原因删除失败,那么更新是不会回滚的。在execute()方法加上@Transactional也没有用,还是上面说的原因,Job每次在需要执行的时候才进行实例化,不被Spring管理。此时我们可以这样,将更新和删除两段代码封装到TaskStoreService服务的同一个方法中,然后在方法上加上@Transactional交给Spring来管理事务,最后在execute()方法中调用封装后的方法。

@Transactional
public void updateAndDelete(TaskStore store, String delId) {
    // 更新
    store.setUpdateDate(new Date());
    taskStoreService.updateById(store); 
    // 删除
    taskStoreService.deleteById(store);
}

(3)、如何监控所有的Job运行和日志的打印?
  可以通过Quartz提供的监听器来实现,本章节提供另外一种自定义方式。Quartz提供的监听器后面的章节中会讲到。
  首先实现org.quartz.Job接口的一个抽象基类,然后重写execute(JobExecutionContext context)方法,再在基类中定义一个抽象方法businessWorking(JobExecutionContext context) ,名字可以自己根据情况取,最后在execute方法中调用businessWorking方法。下面贴上代码:

package com.quartz.task;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 *  基础Job抽象类
 */
public abstract class AbsQuartzJob implements Job{

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // Job运行前监控
        System.out.println("execute before");
        
        // 调用业务代码
        this.businessWorking(context);
        
        // Job运行后监控
        System.out.println("execute after");
    }

    /** 业务代码抽象方法  */
    public abstract void businessWorking(JobExecutionContext context);
}

  开发中的业务Job类直接继承AbsQuartzJob抽象类,业务代码写在继承类的businessWorking()方法中。代码示例:

package com.quartz.task;

import org.quartz.JobExecutionContext;

/** 
 * 业务job
 */
public class BusinessJob extends AbsQuartzJob{

    @Override
    public void businessWorking(JobExecutionContext context) {
        // TODO 业务代码
    }
}

  这里有人可能会问,为什么不使用JDK或CGLIB动态代理呢?我本人之前也这样实现过,但是有个致命的问题,那就是JobDetail被持久化之后会保存到数据库中,所以Job实现类的包路径+类名也会存在数据库中,当项目重启后,所有动态代理的类都自动被虚拟机清除了,此时Quartz框架启动后会加载被持久化的Job,当去访问Job的实现类时,发现已经找不到此实现类了。启动直接报错!所以上面的实现方式可能是相对方便好用一点的方法了。

到此,Job相关类的理解和使用说完了。如果有什么问题,请在下方留言,谢谢!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容