Spring quartz中job相關理解實踐

1.job

首先quartz中有三個重要的模塊,定時任務Job、觸發器Trigger、調度器Scheduler。java

其中job是在觸發器Trigger觸發以後,經調度器Scheduler分配以後執行的任務。算法

2.預約義Job接口

預約義的job接口中,只有execute一個方法。設計模式

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

public interface Job {
    void execute(JobExecutionContext var1) throws JobExecutionException;
}

複製代碼

3.模板方法模式

3.1 什麼是模板方法模式

在抽象類中定義一個操做中的算法骨架,而且把一些步驟延遲到子類。bash

模板方法使得子類能夠不改變一個算法的結構便可從新定義該算法的某些特定步驟ide

3.2 結構圖

3.3 基本代碼實現

//抽象類
public abstract class AbstractClass {

    public abstract void primitiveOperation1();

    public abstract void primitiveOperation2();

    // 模板方法,給出了邏輯的骨架
    // 而邏輯的組成是一些相應的抽象操做,他們都推遲到子類實現
    public void templateMethod() {

        primitiveOperation1();
        primitiveOperation2();
        System.out.println("");
    }
}

//具體實現類

```java
public class ConcreteClassA extends AbstractClass {

    @Override
    public void primitiveOperation1() {

        System.out.println("具體類A方法1實現");
    }

    @Override
    public void primitiveOperation2() {

        System.out.println("具體類A方法2實現");
    }
}
複製代碼

3.4 特色

  • 經過父類調用子類實現的方法(不變的方法),擴展性提升(子類分別實現不一樣算法細節),下降子類中的重複代碼。
  • 主要業務場景:
    • 多個子類有共有的方法,而且邏輯基本相同。
    • 重要、複雜的算法,能夠把核心算法設計爲模板方法,周邊的相關細節功能則由各個子類實現。
    • 重構時,模板方法是一個常用的方法,把相同的代碼抽取到父類中,而後經過構造函數約束其行爲

4.基於模板方法模式的設計實現

4.1 爲何要用模板方法模式

業務場景:
在實際生產中,不一樣的功能性的定時任務是寫在不一樣的Job中,他們經過實現Job接口,而且重寫execute方法實現不一樣定時任務的邏輯。函數

public class QuartzHelloJob implements Job {
 
    private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
 
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(format.format(new Date()) + " Hello Quartz!");
    }
}
複製代碼

可是大部分狀況下,每一個定時任務都有一段公共代碼,如觸發處理jobDetail的邏輯等,因此這裏定義一個抽象Job類,即BaseJob類來屏蔽execute方法,而且直接在execute方法中調用一個新的doJob()方法。這樣的話
1.可以提供一些通用的功能邏輯寫再execute()方法中,方便擴展
2.屏蔽了 JobExecutionContext 與 JobExecutionException學習

4.2 設計實現

本設計是主要運用到了類的繼承,在Job接口中定義execute方法,而後用抽象類BaseJob去繼承,最後讓實現類XXXJob去執行具體的邏輯。子類重寫了父類的方法,若是子類調用該方法,運行的是子類的方法,不會運行父類該方法。ui

public abstract class BaseJob implements Job {
 
    private static final Logger logger = Logger.getLogger(BaseJob.class);
 
    @Override
    public final void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            dojob(XXX);
        } catch (Exception e) {
            logger.error("執行 Job 出錯!", e);
        }
    }
 
    public abstract void dojob(String XXX);
}
複製代碼

XXXJob類實現:this

public class testJob extends BaseJob {
    @Override
    public String doJob(String jobParameters) throws Exception {
         // 各自Job的邏輯實現
    } 
}
複製代碼

5.job是如何執行的

參考:blog.csdn.net/GAMEloft9/a…
blog.csdn.net/GAMEloft9/a…編碼

5.1 基本流程

1. 建立SchedulerFactory
2. 建立Scheduler
3. 建立JobDetail
4. 建立Trigger
5. 註冊到Scheduler:scheduler.scheduleJob(jobDetail, trigger)
6. 啓動Scheduler:scheduler.start()
 
public class RAMJobTest {

    @Test
    public void testExecute() throws SchedulerException, InterruptedException {
        // 1.建立Scheduler的工廠
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        // 2.從工廠中獲取調度器實例
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 3.建立JobDetail
        JobDetail jobDetail = JobBuilder.newJob(RAMJob.class)
                .withDescription("this is a ram job") //job的描述
                .withIdentity("ramJob", "ramGroup") //job 的name和group
                .build();
        // 4.建立Trigger
        Trigger trigger = TriggerBuilder.newTrigger().withDescription("")
                .withIdentity("ramTrigger", "ramTriggerGroup")
                .startAt(new Date()) // 默認當前時間啓動
                .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) // 兩秒執行一次
                .build();
        // 5.註冊任務和定時器
        scheduler.scheduleJob(jobDetail, trigger);
        // 6.啓動調度器
        scheduler.start();
        System.out.println("啓動時間 : " + new Date() + " " + Thread.currentThread().getName());
        Thread.sleep(60000);        
        System.out.println("done");
    }

} 
做者:icameisaw
連接:https://www.jianshu.com/p/3f77224ad9d4
來源:簡書
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

複製代碼

5.2 建立SchedulerFactory及Scheduler

SchedulerFactory是生產Scheduler實例的工廠類

import java.util.Collection;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;

public interface SchedulerFactory {
    Scheduler getScheduler() throws SchedulerException;

    Scheduler getScheduler(String var1) throws SchedulerException;

    Collection<Scheduler> getAllSchedulers() throws SchedulerException;
}
這個接口有兩個實現類:
StdSchedulerFacotory經過配置文件來設置Scheduler的各項參數
DirectSchedulerFactory主要經過硬編碼來設置Scheduler的各項參數
複製代碼

本項目使用的是StdSchedulerFacotory:

建立Scheduler實例的流程:

www.jianshu.com/p/760a96048…

5.3 建立Jobdetail和trigger

JobDetailImpl.java

JobDetailImpl是接口JobDetail的惟一實現類,本質上來講是一個Java Bean,這裏主要是要理解各個屬性的意思。

public class JobDetailImpl implements Cloneable, java.io.Serializable, JobDetail {

    // Job的名稱
    private String name;

    // Job的分組
    private String group = Scheduler.DEFAULT_GROUP;

    // Job的描述
    private String description;

    // 執行Job業務邏輯的對應實體類的Class引用
    private Class<? extends Job> jobClass;

    // 保存關於Job的信息,根據業務邏輯本身放進去
    private JobDataMap jobDataMap;

    // 當沒有綁定Trigger的狀況,是否保存Job
    private boolean durability = false;

    // Job是否可從「恢復」狀況下再次執行
    private boolean shouldRecover = false;

    // 封裝了name和group,做爲JobDetail的惟一標識
    // 用空間來換取時間和可讀性的策略
    private transient JobKey key = null;
}
複製代碼

JobDataMap

JobDataMap提供了一個Map<String, Object>的對象,咱們能夠在管理或者執行Job的過程當中保存或者查詢一些自定義的信息。

JobBuilder

  • 將JobDetail的屬性都封裝起來
  • 容許JobDeatil經過多個步驟來建立,而且能夠改變過程或者順序
// 建立JobDetail
JobDetail jobDetail = JobBuilder.newJob(RAMJob.class)
        .withDescription("this is a ram job") //job的描述
        .withIdentity("ramJob", "ramGroup") //job 的name和group
        .build();
        
// build方法
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;
}

//jobDataMap字段屬性
public class JobBuilder {
    public JobBuilder withIdentity(String name) public JobBuilder withIdentity(String name, String group) public JobBuilder withIdentity(JobKey jobKey) public JobBuilder withDescription(String jobDescription) public JobBuilder ofType(Class<? extends Job> jobClazz) public JobBuilder requestRecovery() public JobBuilder requestRecovery(boolean jobShouldRecover) public JobBuilder storeDurably() public JobBuilder storeDurably(boolean jobDurability) public JobBuilder usingJobData(String dataKey, String value) public JobBuilder usingJobData(String dataKey, Integer value) public JobBuilder usingJobData(String dataKey, Long value) public JobBuilder usingJobData(String dataKey, Float value) public JobBuilder usingJobData(String dataKey, Double value) public JobBuilder usingJobData(String dataKey, Boolean value) public JobBuilder usingJobData(JobDataMap newJobDataMap) public JobBuilder setJobData(JobDataMap newJobDataMap) } 複製代碼

Trigger相關

Trigger trigger = TriggerBuilder.newTrigger().withDescription("")
    .withIdentity("ramTrigger", "ramTriggerGroup")
    .startAt(new Date()) // 默認當前時間啓動
    .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) // 兩秒執行一次
    .usingJobData("triggerKey", "some important information to save")
    .build();
複製代碼

TriggerBuilder

TriggerBuilder是一個泛型類,與JobBuilder有點不同,並且建立Trigger實例的動做委託給了ScheduleBuilder類。ScheduleBuilder,此次不顧名思義了,它做爲一個生成器,不是要生成Scheduler類,而是要生成MutableTrigger實例。

5.4 註冊到Scheduler

scheduler.scheduleJob(jobDetail, trigger)
複製代碼

流程以下:

StdScheduler

StdScheduler的方法基本上都代理給QuartzScheduler類來處理

QuartzScheduler

org.quartz.Scheduler接口的間接實現,quartz的核心調度類,任務的調度和任務的管理都是QuartzScheduler實現的,而後經過一個靜態代理類StdScheduler提供出來。

public class QuartzScheduler implements RemotableQuartzScheduler {

    // QuartzSchedulerResources對象是經過構造器放進去的
    public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval) throws SchedulerException {
        this.resources = resources;
        if (resources.getJobStore() instanceof JobListener) {
            addInternalJobListener((JobListener)resources.getJobStore());
        }

        this.schedThread = new QuartzSchedulerThread(this, resources);
        ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
        schedThreadExecutor.execute(this.schedThread);
        if (idleWaitTime > 0) {
            this.schedThread.setIdleWaitTime(idleWaitTime);
        }

        jobMgr = new ExecutingJobsManager();
        addInternalJobListener(jobMgr);
        errLogger = new ErrorLogger();
        addInternalSchedulerListener(errLogger);

        signaler = new SchedulerSignalerImpl(this, this.schedThread);

        getLog().info("Quartz Scheduler v." + getVersion() + " created.");
    }

    public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException {
        validateState();

        if (jobDetail == null) {
            throw new SchedulerException("JobDetail cannot be null");
        }
        if (trigger == null) {
            throw new SchedulerException("Trigger cannot be null");
        }
        if (jobDetail.getKey() == null) {
            throw new SchedulerException("Job's key cannot be null");
        }
        if (jobDetail.getJobClass() == null) {
            throw new SchedulerException("Job's class cannot be null");
        }
        // TriggerBuilder.build()會生成一個OperableTrigger實例。
        OperableTrigger trig = (OperableTrigger)trigger;

        if (trigger.getJobKey() == null) {
            trig.setJobKey(jobDetail.getKey());
        } else if (!trigger.getJobKey().equals(jobDetail.getKey())) {
            throw new SchedulerException(
                "Trigger does not reference given job!");
        }

        trig.validate();

        Calendar cal = null;
        if (trigger.getCalendarName() != null) {
            cal = resources.getJobStore().retrieveCalendar(trigger.getCalendarName());
        }
        // TODO: 解析各類類型的Trigger
        Date ft = trig.computeFirstFireTime(cal);

        if (ft == null) {
            throw new SchedulerException(
                    "Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire.");
        }
            // 關鍵代碼就是下面這一行
        resources.getJobStore().storeJobAndTrigger(jobDetail, trig);
        notifySchedulerListenersJobAdded(jobDetail);
        notifySchedulerThread(trigger.getNextFireTime().getTime());
        notifySchedulerListenersSchduled(trigger);

        return ft;
    }

    // 其餘代碼

}
複製代碼

scheduler.start()

public class QuartzScheduler implements RemotableQuartzScheduler {

    public void start() throws SchedulerException {
        if (shuttingDown|| closed) {
            throw new SchedulerException(
                    "The Scheduler cannot be restarted after shutdown() has been called.");
        }

        // QTZ-212 : calling new schedulerStarting() method on the listeners
        // right after entering start()
        notifySchedulerListenersStarting();

        if (initialStart == null) {//初始化標識爲null,進行初始化操做
            initialStart = new Date();
            // RAMJobStore 啥都不作
            // JobStoreSupport 判斷是否集羣,恢復Job等
            this.resources.getJobStore().schedulerStarted();           
            startPlugins();
        } else {
            resources.getJobStore().schedulerResumed();// 若是已經初始化過,則恢復jobStore
        }

        schedThread.togglePause(false);// 喚醒全部等待的線程

        getLog().info("Scheduler " + resources.getUniqueIdentifier() + " started.");

        notifySchedulerListenersStarted();
    }

    // 其餘代碼 
}
複製代碼

QuartzSchedulerThread

public class QuartzSchedulerThread extends Thread {

    /**
     * pause爲true,發出讓主循環暫停的信號,以便線程在下一個可處理的時刻暫停
     * pause爲false,喚醒sigLock對象的全部等待隊列的線程
     */
    void togglePause(boolean pause) {
        synchronized (sigLock) {
            paused = pause;

            if (paused) {
                signalSchedulingChange(0);
            } else {
                sigLock.notifyAll();
            }
        }
    }

    // 其餘代碼

}
複製代碼

listener事件監聽

Listener事件監聽是觀察者模式的一個應用

QuartzScheduler的scheduleJob()start()方法都有notifyXXX代碼邏輯,這些就是JobDetail、Trigger和Scheduler事件監聽的代碼邏輯。
在《Scheduler的初始化》篇章裏面,初始化一個Scheduler,裏面有"根據PropertiesParser建立Listeners"的步驟,**Listeners就包括JobListener和TriggerListener的List對象**。
SchedulerListener不支持配置在quartz.properties裏面,初始化Scheduler的過程當中沒有這一塊的代碼邏輯。若是要添加一個觀察者,那麼能夠**經過StdScheduler.getListenerManager()獲取ListenerManager實例**,經過它能夠拿到全部觀察者的引用。
 
複製代碼

Subject通知Observer,都是遍歷Observer列表,觸發相應的通知,實現事件監聽的效果。

5.5 QuartzScheduler中建立啓動QuartzSchedulerThread

何時建立

  • StdSchedulerFactory.instantiate():生產StdScheduler過程當中會new一個QuartzScheduler實例
qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
複製代碼
  • 在QuartzScheduler的構造器方法裏面能夠看到建立QuartzSchedulerThread的代碼邏輯,並經過QuartzSchedulerResources對象獲取ThreadExecutor對象,最後execute新建的QuartzSchedulerThread。
// QuartzSchedulerThread建立和啓動的邏輯
this.schedThread = new QuartzSchedulerThread(this, resources);
ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
schedThreadExecutor.execute(this.schedThread);
複製代碼
  • DefaultThreadExecutor是ThreadExecutor接口的惟一實現類,傳入指定的Thread對象,便啓動該線程。到這裏,QuartzSchedulerThread啓動了。
public class DefaultThreadExecutor implements ThreadExecutor {

    public void initialize() {
    }

    public void execute(Thread thread) {
        thread.start();
    }

}
複製代碼

QuartzSchedulerThread中run()方法

  • 建立JobRunShell
  • ThreadPool.runInThread()
  • WorkerThread.run(runnable) (WorkerThread的初始化):
    • StdSchedulerFactory.instantiate()建立了ThreadPool tp
    • tp.initialize()裏面有初始化WorkerThread的邏輯
  • WorkerThread.run()
  • JobRunShell.run()

5.6 Job執行狀態

STATE_BLOCKED 4 阻塞 STATE_COMPLETE 2 完成 STATE_ERROR 3 錯誤 STATE_NONE -1 不存在 STATE_NORMAL 0 正常 STATE_PAUSED 1 暫停

Scheduler scheduler = schedulerFactoryBean.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
TriggerState state = scheduler.getTriggerState(triggerKey);
複製代碼

n.總結

  • 設計模式的學習最好能結合業務場景同時理解。

  • @Component這個註解的做用標記這個類能夠被IOC容器管理,類上加上此標記可以讓Spring掃描到。

  • 參考:www.jianshu.com/p/38e5e0953…

相關文章
相關標籤/搜索