领略Quartz源码架构之美——源码实弹之Job

本章阅读收获:可了解Quartz框架中的Job部分源码

源码起航

人之初,专门找软柿子捏,所以我就又忍不住先拿Job进行开刀,作为一个敲门砖进行源码分析。

Quartz中的Job是什么?

联系自己对于定时任务的理解,其实就是对于任务的抽象,所以这个类其实你在不看源码时,可能就已经就猜到了它是一个接口,一搜源码,果然没错:

package org.quartz;

/**
 * 定时任务对于任务的抽象
 */
public interface Job {

    /**
     * 定时任务执行逻辑
     */
    void execute(JobExecutionContext context)
        throws JobExecutionException;

}

非常简单,是否你再没有看之前,自己也已经构想到了呢?但是这边又引发了另一个类不接,就是JobExecutionContext,但是细想一下,你可能也会觉得在情理之中,因为在执行任务中,可能你需要获取任务信息。然后你可能会想,比如呢? 在生产环境中,你可能会用到分布式的定时任务,那么你如何让任务判断自己改处理哪些数据信息呢,这个时候你可以获取Job的上下文,针对取余等等方式,避免数据重复处理。

JobExecutionContext源码解析

首先这也是一个接口,由于内部方法有点多,我们挑一个最重要的分析一下:


package org.quartz;

import java.util.Date;

/**
 * Job任务执行时获取到的上下文
 */
public interface JobExecutionContext {
    ...
    
    /**
     * 获取任务详细信息
     */
    public JobDetail getJobDetail();
    
    ...

}

对于这个类的实现类,默认是JobExecutionContextImpl,由于篇幅原因,这个就不做特地展开,因为这个类只是简单的把属性取出,很简单。那接下来,便开始解决读者的下一个以为,什么是JobDetail?

JobDetail源码解析

你可以先猜想一下,这个接口中会包含哪些信息?

我们直接进入它的实现类:

package org.quartz.impl;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.Scheduler;
import org.quartz.StatefulJob;
import org.quartz.Trigger;
import org.quartz.utils.ClassUtils;


/**
 * 任务相信信息
 */
public class JobDetailImpl implements Cloneable, java.io.Serializable, JobDetail {

    private static final long serialVersionUID = -6069784757781506897L;
    /**
     * 任务名称
     */
    private String name;

    /**
     * 任务所属组
     */
    private String group = Scheduler.DEFAULT_GROUP;

    /**
     * 任务描述
     */
    private String description;

    /**
     * 任务类
     */
    private Class<? extends Job> jobClass;

    /**
     * 任务额外信息
     */
    private JobDataMap jobDataMap;

    private boolean durability = false;

    private boolean shouldRecover = false;

    /**
     * 任务唯一标识
     */
    private transient JobKey key = null;

    public JobDetailImpl() {

    }

    public JobDetailImpl(String name, Class<? extends Job> jobClass) {
        this(name, null, jobClass);
    }


    public JobDetailImpl(String name, String group, Class<? extends Job> jobClass) {
        setName(name);
        setGroup(group);
        setJobClass(jobClass);
    }

    public JobDetailImpl(String name, String group, Class<? extends Job> jobClass,
                     boolean durability, boolean recover) {
        setName(name);
        setGroup(group);
        setJobClass(jobClass);
        setDurability(durability);
        setRequestsRecovery(recover);
    }


    public String getName() {
        return name;
    }


    public void setName(String name) {
        if (name == null || name.trim().length() == 0) {
            throw new IllegalArgumentException("Job name cannot be empty.");
        }

        this.name = name;
        this.key = null;
    }


    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        if (group != null && group.trim().length() == 0) {
            throw new IllegalArgumentException(
                    "Group name cannot be empty.");
        }

        if (group == null) {
            group = Scheduler.DEFAULT_GROUP;
        }

        this.group = group;
        this.key = null;
    }


    public String getFullName() {
        return group + "." + name;
    }


    public JobKey getKey() {
        if(key == null) {
            if(getName() == null)
                return null;
            key = new JobKey(getName(), getGroup());
        }

        return key;
    }
    
    public void setKey(JobKey key) {
        if(key == null)
            throw new IllegalArgumentException("Key cannot be null!");

        setName(key.getName());
        setGroup(key.getGroup());
        this.key = key;
    }

    public String getDescription() {
        return description;
    }


    public void setDescription(String description) {
        this.description = description;
    }

    public Class<? extends Job> getJobClass() {
        return jobClass;
    }


    public void setJobClass(Class<? extends Job> jobClass) {
        if (jobClass == null) {
            throw new IllegalArgumentException("Job class cannot be null.");
        }

        if (!Job.class.isAssignableFrom(jobClass)) {
            throw new IllegalArgumentException(
                    "Job class must implement the Job interface.");
        }

        this.jobClass = jobClass;
    }


    public JobDataMap getJobDataMap() {
        if (jobDataMap == null) {
            jobDataMap = new JobDataMap();
        }
        return jobDataMap;
    }


    public void setJobDataMap(JobDataMap jobDataMap) {
        this.jobDataMap = jobDataMap;
    }
    
   ...

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof JobDetail)) {
            return false;
        }

        JobDetail other = (JobDetail) obj;

        if(other.getKey() == null || getKey() == null)
            return false;
        
        if (!other.getKey().equals(getKey())) {
            return false;
        }
            
        return true;
    }

    @Override
    public int hashCode() {
        JobKey key = getKey();
        return key == null ? 0 : getKey().hashCode();
    }
    
    @Override
    public Object clone() {
        JobDetailImpl copy;
        try {
            copy = (JobDetailImpl) super.clone();
            if (jobDataMap != null) {
                copy.jobDataMap = (JobDataMap) jobDataMap.clone();
            }
        } catch (CloneNotSupportedException ex) {
            throw new IncompatibleClassChangeError("Not Cloneable.");
        }

        return copy;
    }

    public JobBuilder getJobBuilder() {
        JobBuilder b = JobBuilder.newJob()
            .ofType(getJobClass())
            .requestRecovery(requestsRecovery())
            .storeDurably(isDurable())
            .usingJobData(getJobDataMap())
            .withDescription(getDescription())
            .withIdentity(getKey());
        return b;
    }
}

相信大家对于这些属性部分可以猜到,先讲一个大家可能没有太注意的Java关键字,是在属性key上的transient,这个关键字是用来干什么的呢?是针对于序列化与反序列化时,不对这个字段进行序列化操作的。这么做是为什么呢?我猜测是为了安全起见,以为定时任务是以这个key作为唯一标示的,序列化下把信息泄露可能会造成安全隐患。也可以看下这里重写了hasCode和equals方法,至于为什么这么做,大家应该能意会。这边大家可能对于JobBuilder有些好奇?

JobBuilder源码解析

JobBuilder是定时任务构造器,用于构造一个JobDetail,源码如下:

package org.quartz;

import org.quartz.impl.JobDetailImpl;
import org.quartz.utils.Key;


/**
 * 定时任务构造器,用于构造一个JobDetail
 */
public class JobBuilder {

    /**
     * 任务标识码,用于唯一确定任务
     */
    private JobKey key;
    private String description;
    private Class<? extends Job> jobClass;
    /**
     * Durability,持久性;如果Job是非持久性的,一旦没有Trigger与其相关联,
     * 它就会从Scheduler中被删除。也就是说Job的生命周期和其Trigger是关联的。
     */
    private boolean durability;
    /**
     * RequestsRecovery,如果为true,那么在Scheduler异常中止或者系统异常关闭后,当Scheduler重启后,Job会被重新执行。
     */
    private boolean shouldRecover;
    
    private JobDataMap jobDataMap = new JobDataMap();
    
    protected JobBuilder() {
    }

    public static JobBuilder newJob() {
        return new JobBuilder();
    }
    

    public static JobBuilder newJob(Class <? extends Job> jobClass) {
        JobBuilder b = new JobBuilder();
        b.ofType(jobClass);
        return b;
    }

    public JobDetail build() {

        JobDetailImpl job = new JobDetailImpl();
        
        job.setJobClass(jobClass);
        job.setDescription(description);
        if(key == null)
            key = new JobKey(Key.createUniqueName(null), null);
        job.setKey(key); 
        job.setDurability(durability);
        job.setRequestsRecovery(shouldRecover);
        
        
        if(!jobDataMap.isEmpty())
            job.setJobDataMap(jobDataMap);
        
        return job;
    }

    public JobBuilder withIdentity(String name) {
        key = new JobKey(name, null);
        return this;
    }  
    

    public JobBuilder withIdentity(String name, String group) {
        key = new JobKey(name, group);
        return this;
    }
    

    public JobBuilder withIdentity(JobKey jobKey) {
        this.key = jobKey;
        return this;
    }

    public JobBuilder withDescription(String jobDescription) {
        this.description = jobDescription;
        return this;
    }
    

    public JobBuilder ofType(Class <? extends Job> jobClazz) {
        this.jobClass = jobClazz;
        return this;
    }
  ...
}

一些简单的get、set方法被我删除了,可以看到这个类非常简单,就是作为一个构造器,根据属性构造出一个JobDetail。

结束语

通过本章,我们可以逐步了解Job、JobExecutionContext、 JobDetail、JobBuilder的源码,可以大致掌握Job相关的源码信息。大家也可以回想下,如果是你,你在架构定时框架时,任务这块会如何架构,哪些地方你可以借鉴,哪些地方你可以做的更好~~~

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

推荐阅读更多精彩内容

  • 本文是根据 Quartz定时器官方文档翻译的,只翻译了第1到第10课,如有翻译不精确的地方,请读者指正,互相学习,...
    ChinaXieShuai阅读 8,283评论 1 19
  • scheduler定时调度系统是大多行业项目都需要的,传统的spring-job模式,个人感觉已经out了,因为存...
    安琪拉_4b7e阅读 2,821评论 4 6
  • 那天上午,我照例在厨房刷碗,忽闻狗儿叫声,抬眼望向隔壁腌制咸菜厂的沙地上有几只狗儿在叫唤,再定眼看时,我知道怎么回...
    小墉正阅读 1,298评论 105 105
  • 夜晚时,在一个城市中,所有人都已经入眠,突然房屋开始晃动。所以人都本能从房屋中突然的跑出来。 不知道是谁惊...
    王子毅Theone阅读 483评论 0 0