領略Quartz源碼架構之美——源碼實彈之Job

本章閱讀收獲:可了解Quartz框架中的Job部分源碼

源碼起航

人之初蝎抽,專門找軟柿子捏,所以我就又忍不住先拿Job進行開刀,作為一個敲門磚進行源碼分析樟结。

Quartz中的Job是什么养交?

聯(lián)系自己對于定時任務的理解,其實就是對于任務的抽象瓢宦,所以這個類其實你在不看源碼時碎连,可能就已經(jīng)就猜到了它是一個接口,一搜源碼驮履,果然沒錯:

package org.quartz;

/**
 * 定時任務對于任務的抽象
 */
public interface Job {

    /**
     * 定時任務執(zhí)行邏輯
     */
    void execute(JobExecutionContext context)
        throws JobExecutionException;

}

非常簡單鱼辙,是否你再沒有看之前,自己也已經(jīng)構想到了呢玫镐?但是這邊又引發(fā)了另一個類不接倒戏,就是JobExecutionContext,但是細想一下恐似,你可能也會覺得在情理之中杜跷,因為在執(zhí)行任務中,可能你需要獲取任務信息矫夷。然后你可能會想葛闷,比如呢? 在生產(chǎn)環(huán)境中双藕,你可能會用到分布式的定時任務淑趾,那么你如何讓任務判斷自己改處理哪些數(shù)據(jù)信息呢,這個時候你可以獲取Job的上下文蔓彩,針對取余等等方式治笨,避免數(shù)據(jù)重復處理。

JobExecutionContext源碼解析

首先這也是一個接口赤嚼,由于內(nèi)部方法有點多旷赖,我們挑一個最重要的分析一下:


package org.quartz;

import java.util.Date;

/**
 * Job任務執(zhí)行時獲取到的上下文
 */
public interface JobExecutionContext {
    ...
    
    /**
     * 獲取任務詳細信息
     */
    public JobDetail getJobDetail();
    
    ...

}

對于這個類的實現(xiàn)類,默認是JobExecutionContextImpl更卒,由于篇幅原因等孵,這個就不做特地展開,因為這個類只是簡單的把屬性取出蹂空,很簡單俯萌。那接下來,便開始解決讀者的下一個以為上枕,什么是JobDetail咐熙?

JobDetail源碼解析

你可以先猜想一下,這個接口中會包含哪些信息辨萍?

我們直接進入它的實現(xiàn)類:

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與其相關聯(lián)谓着,
     * 它就會從Scheduler中被刪除。也就是說Job的生命周期和其Trigger是關聯(lián)的坛掠。
     */
    private boolean durability;
    /**
     * RequestsRecovery赊锚,如果為true,那么在Scheduler異常中止或者系統(tǒng)異常關閉后屉栓,當Scheduler重啟后舷蒲,Job會被重新執(zhí)行。
     */
    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方法被我刪除了牲平,可以看到這個類非常簡單,就是作為一個構造器域滥,根據(jù)屬性構造出一個JobDetail纵柿。

結束語

通過本章,我們可以逐步了解Job启绰、JobExecutionContext昂儒、 JobDetail、JobBuilder的源碼委可,可以大致掌握Job相關的源碼信息渊跋。大家也可以回想下,如果是你,你在架構定時框架時刹枉,任務這塊會如何架構叽唱,哪些地方你可以借鑒,哪些地方你可以做的更好~~~

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末微宝,一起剝皮案震驚了整個濱河市棺亭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蟋软,老刑警劉巖镶摘,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異岳守,居然都是意外死亡凄敢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門湿痢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涝缝,“玉大人,你說我怎么就攤上這事譬重【艽” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵臀规,是天一觀的道長滩援。 經(jīng)常有香客問我,道長塔嬉,這世上最難降的妖魔是什么玩徊? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮谨究,結果婚禮上恩袱,老公的妹妹穿的比我還像新娘。我一直安慰自己记盒,他們只是感情好憎蛤,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纪吮,像睡著了一般俩檬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碾盟,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天棚辽,我揣著相機與錄音,去河邊找鬼冰肴。 笑死屈藐,一個胖子當著我的面吹牛榔组,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播联逻,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼搓扯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了包归?” 一聲冷哼從身側響起锨推,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎公壤,沒想到半個月后换可,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡厦幅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年沾鳄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片确憨。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡译荞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缚态,到底是詐尸還是另有隱情磁椒,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布玫芦,位于F島的核電站,受9級特大地震影響本辐,放射性物質(zhì)發(fā)生泄漏桥帆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一慎皱、第九天 我趴在偏房一處隱蔽的房頂上張望老虫。 院中可真熱鬧,春花似錦茫多、人聲如沸祈匙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夺欲。三九已至,卻和暖如春今膊,著一層夾襖步出監(jiān)牢的瞬間些阅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工斑唬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留市埋,地道東北人黎泣。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像缤谎,于是被迫代替她去往敵國和親抒倚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 本文是根據(jù) Quartz定時器官方文檔翻譯的坷澡,只翻譯了第1到第10課托呕,如有翻譯不精確的地方,請讀者指正洋访,互相學習镣陕,...
    ChinaXieShuai閱讀 8,367評論 1 19
  • scheduler定時調(diào)度系統(tǒng)是大多行業(yè)項目都需要的,傳統(tǒng)的spring-job模式姻政,個人感覺已經(jīng)out了呆抑,因為存...
    安琪拉_4b7e閱讀 2,841評論 4 6
  • 那天上午,我照例在廚房刷碗汁展,忽聞狗兒叫聲鹊碍,抬眼望向隔壁腌制咸菜廠的沙地上有幾只狗兒在叫喚,再定眼看時食绿,我知道怎么回...
    小墉正閱讀 1,303評論 105 105
  • 夜晚時侈咕,在一個城市中,所有人都已經(jīng)入眠器紧,突然房屋開始晃動耀销。所以人都本能從房屋中突然的跑出來。 不知道是誰驚...
    王子毅Theone閱讀 493評論 0 0