quart初始化流程

研究源碼金砍,從簡單使用開始局蚀,跑一遍demo后,再研究是如何初始化的恕稠,我們先研究以下的代碼:

public class QuartzSimpleDemo {

    public static void main(String[] args) throws SchedulerException {
        //1\. 創(chuàng)建Scheduler
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler sched  = sfact.getScheduler();

        //2\. 創(chuàng)建job信息
        JobDetail testTaskJob = JobBuilder.newJob(TestTaskJob.class).storeDurably()
                .withIdentity("TestTaskJob").build();
        //3\. 創(chuàng)建觸發(fā)器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?");
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .forJob(testTaskJob)
                .withIdentity("testTaskJob")
                .withSchedule(cronScheduleBuilder)
                .build();
        //4\. 啟動
        sched.scheduleJob(testTaskJob, cronTrigger);
        sched.start();
    }
}

quartz原生初始化

[圖片上傳中...(image-97984b-1565345621214-0)]

1. 創(chuàng)建Scheduler

SchedulerFactory sfact = new StdSchedulerFactory();

工廠模式創(chuàng)建一個Scheduler工廠琅绅,quartz中有兩個實現(xiàn)類,DirectSchedulerFactory和StdSchedulerFactory鹅巍,常用StdSchedulerFactory類千扶。

Scheduler sched  = sfact.getScheduler();

從工廠中獲取一個Scheduler對象,這個是我們研究的重點骆捧。我們看下getSchedulerd的邏輯

public Scheduler getScheduler() throws SchedulerException {
        //初始化配置文件
            if (cfg == null) {
            initialize();
        }

        SchedulerRepository schedRep = SchedulerRepository.getInstance();
            //根據(jù)scheduler名字從緩存中獲取Scheduler對象
        Scheduler sched = schedRep.lookup(getSchedulerName());

        if (sched != null) {
            if (sched.isShutdown()) {
                schedRep.remove(getSchedulerName());
            } else {
                return sched;
            }
        }

            //如果緩存中沒有澎羞,則初始化
        sched = instantiate();

        return sched;
    }

1.1 初始化配置

  • 如果創(chuàng)建SchedulerFactory時,傳了配置文件凑懂,則使用傳入的

  • 如果創(chuàng)建SchedulerFactory時沒有傳煤痕,則看系統(tǒng)參數(shù)中配置了org.quartz.properties,也就-Dorg.quartz.properties是否配置接谨。

  • 如果系統(tǒng)參數(shù)沒有傳摆碉,或者傳了找不到,則在各種路徑中查找quartz.properties文件脓豪。所以建議直接在配置文件中新增quartz.properties文件就可以巷帝。

1.2 緩存中加載Scheduler

上面代碼是從SchedulerRepository獲取緩存的Scheduler,SchedulerRepository中有一個全局的HashMap扫夜,key是Scheduler名楞泼,值是Scheduler對象驰徊,開始時肯定是空,所以調(diào)用instantiate方法.

1.3 初始化

如果緩存中沒有堕阔,則創(chuàng)建 Scheduler對象棍厂,看下創(chuàng)建邏輯,創(chuàng)建邏輯主要是讀取配置超陆,根據(jù)配置創(chuàng)建對應(yīng)的類.這個方法有點長牺弹,不過注釋寫的好,很容易看懂,我留下主要的代碼

private Scheduler instantiate() throws SchedulerException {
        // Get Scheduler Properties
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // If Proxying to remote scheduler, short-circuit here...
        // ~~~~~~~~~~~~~~~~~

        // Create class load helper

        // If Proxying to remote JMX scheduler, short-circuit here...
        // ~~~~~~~~~~~~~~~~~~

        // Get ThreadPool Properties
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // Get JobStore Properties
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // Set up any DataSources
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // Set up any SchedulerPlugins
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // Set up any JobListeners
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // Set up any TriggerListeners
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // Get ThreadExecutor Properties
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // Fire everything up
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            // Create correct run-shell factory...

       //create QuartzSchedulerResources
       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       QuartzSchedulerResources rsrcs = new QuartzSchedulerResources();
       //create QuartzScheduler
       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
        qsInited = true;

        // Create Scheduler ref...
        Scheduler scheduler = instantiate(rsrcs, qs);

        schedRep.bind(scheduler);
        return scheduler;
        }
    }

上面的代碼时呀,我把代碼都刪了张漂,留下注釋和幾行核心的代碼,看起來更清晰谨娜。主要邏輯是根據(jù)配置創(chuàng)建不同的類航攒。然后把所有的類都封裝到QuartzSchedulerResources對象中。然后再創(chuàng)建QuartzScheduler對象趴梢。Scheduler大多數(shù)操作都是依賴QuartzScheduler完成的漠畜。最后創(chuàng)建Scheduler對象,并把Scheduler對象存到緩存中坞靶。

QuartzScheduler創(chuàng)建時啟動了生產(chǎn)者線程盆驹,到研究quartz線程模型的時候會寫到.

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);
        }
    }

2. 創(chuàng)建JobDetail

利用構(gòu)造者模式創(chuàng)建JobDetail對象,比較簡單

3. 創(chuàng)建Trigger

利用構(gòu)造者模式創(chuàng)建Trigger對象,比較簡單。

4. 調(diào)度Job

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");
        }

        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());
        }
        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;
    }

job調(diào)度滩愁,開始參數(shù)校驗,然后存儲job和trigger辫封,然后通知各種監(jiān)聽器硝枉。

5. 啟動

啟動主要也是通知監(jiān)聽事情,表示Scheduler啟動起來了倦微。

spring boot quartz初始化

1. 查找配置類

spring boot quartz的使用我們并沒有使用任何注解就能夠使用妻味,那它是如何初始化的呢?因為使用了spring boot欣福,所以我猜測使用了spring boot的自動配置功能责球,所以我用IDEA搜索了QuartzAutoConfiguration,還真讓我搜到了拓劝。如果不知道spring boot的加載過程雏逾,可以查看SchedulerFactory#getScheduler被哪里調(diào)用了,反推初始化過程郑临。

@Configuration
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class })
@EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class QuartzAutoConfiguration {}

QuartzAutoConfiguration使用了@Configuration注解栖博,在spring boot啟動的時候就會加載,當(dāng)系統(tǒng)中有Scheduler相關(guān)的類時厢洞,就會創(chuàng)建對應(yīng)的bean仇让。

2. 構(gòu)造函數(shù)

public QuartzAutoConfiguration(QuartzProperties properties,
            ObjectProvider<SchedulerFactoryBeanCustomizer> customizers, ObjectProvider<JobDetail[]> jobDetails,
            Map<String, Calendar> calendars, ObjectProvider<Trigger[]> triggers,
            ApplicationContext applicationContext) {
        this.properties = properties;
        this.customizers = customizers;
        this.jobDetails = jobDetails.getIfAvailable();
        this.calendars = calendars;
        this.triggers = triggers.getIfAvailable();
        this.applicationContext = applicationContext;
    }

構(gòu)造函數(shù)中注入了jobDetail和trigger典奉。

3. SchedulerFactoryBean

@Bean
    @ConditionalOnMissingBean
    public SchedulerFactoryBean quartzScheduler() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
        jobFactory.setApplicationContext(this.applicationContext);
        schedulerFactoryBean.setJobFactory(jobFactory);
        if (this.properties.getSchedulerName() != null) {
            schedulerFactoryBean.setSchedulerName(this.properties.getSchedulerName());
        }
        schedulerFactoryBean.setAutoStartup(this.properties.isAutoStartup());
        schedulerFactoryBean.setStartupDelay((int) this.properties.getStartupDelay().getSeconds());
        schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(this.properties.isWaitForJobsToCompleteOnShutdown());
        schedulerFactoryBean.setOverwriteExistingJobs(this.properties.isOverwriteExistingJobs());
        if (!this.properties.getProperties().isEmpty()) {
            schedulerFactoryBean.setQuartzProperties(asProperties(this.properties.getProperties()));
        }
        if (this.jobDetails != null && this.jobDetails.length > 0) {
            schedulerFactoryBean.setJobDetails(this.jobDetails);
        }
        if (this.calendars != null && !this.calendars.isEmpty()) {
            schedulerFactoryBean.setCalendars(this.calendars);
        }
        if (this.triggers != null && this.triggers.length > 0) {
            schedulerFactoryBean.setTriggers(this.triggers);
        }
        customize(schedulerFactoryBean);
        return schedulerFactoryBean;
    }

聲明了一個SchedulerFactoryBean bean,設(shè)置了配置丧叽、jobDetail和trigger屬性值卫玖。接下來看下SchedulerFactoryBean的初始化過程。

public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>,
        BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
    ...
}

SchedulerFactoryBean實現(xiàn)了InitializingBean和FactoryBean踊淳,所以在初始化的時候會調(diào)用afterPropertiesSet方法假瞬,在得到bena的時候會調(diào)用getObject方法,先看afterPropertiesSet方法嚣崭。

@Override
    public void afterPropertiesSet() throws Exception {
        if (this.dataSource == null && this.nonTransactionalDataSource != null) {
            this.dataSource = this.nonTransactionalDataSource;
        }

        if (this.applicationContext != null && this.resourceLoader == null) {
            this.resourceLoader = this.applicationContext;
        }

        // Initialize the Scheduler instance...
        this.scheduler = prepareScheduler(prepareSchedulerFactory());
        try {
            registerListeners();
            registerJobsAndTriggers();
        }
        catch (Exception ex) {
            try {
                this.scheduler.shutdown(true);
            }
            catch (Exception ex2) {
                logger.debug("Scheduler shutdown exception after registration failure", ex2);
            }
            throw ex;
        }
    }

看到初始化scheduler的注釋笨触,所以應(yīng)該就是在prepareScheduler中初始化scheduler對象的。在創(chuàng)建scheduler之前雹舀,先創(chuàng)建了SchedulerFactory芦劣,看下創(chuàng)建過程。

private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
        SchedulerFactory schedulerFactory = this.schedulerFactory;
        if (schedulerFactory == null) {
            // Create local SchedulerFactory instance (typically a StdSchedulerFactory)
            schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
            if (schedulerFactory instanceof StdSchedulerFactory) {
                initSchedulerFactory((StdSchedulerFactory) schedulerFactory);
            }
            else if (this.configLocation != null || this.quartzProperties != null ||
                    this.taskExecutor != null || this.dataSource != null) {
                throw new IllegalArgumentException(
                        "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
            }
            // Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties)
        }
        // Otherwise, assume that externally provided factory has been initialized with appropriate settings
        return schedulerFactory;
    }

在SchedulerFactoryBean創(chuàng)建的時候并沒有給schedulerFactory屬性賦值说榆,所以schedulerFactory是空的虚吟,如果是空的則加載schedulerFactoryClass類,schedulerFactoryClass的值是StdSchedulerFactory签财。這和我們直接使用quartz是一樣的串慰。接下來就是創(chuàng)建Scheduler對象。

private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException {
        ...
        // Get Scheduler instance from SchedulerFactory.
        try {
            Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName);
            ...
            return scheduler;
        }

prepareScheduler方法又調(diào)用了createScheduler唱蒸,繼續(xù)跟蹤

protected Scheduler createScheduler(SchedulerFactory schedulerFactory, @Nullable String schedulerName)
            throws SchedulerException {

        // Override thread context ClassLoader to work around naive Quartz ClassLoadHelper loading.
        Thread currentThread = Thread.currentThread();
        ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
        boolean overrideClassLoader = (this.resourceLoader != null &&
                this.resourceLoader.getClassLoader() != threadContextClassLoader);
        if (overrideClassLoader) {
            currentThread.setContextClassLoader(this.resourceLoader.getClassLoader());
        }
        try {
      //從緩存中獲取Scheduler
            SchedulerRepository repository = SchedulerRepository.getInstance();
            synchronized (repository) {
                Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null);
                Scheduler newScheduler = schedulerFactory.getScheduler();
                if (newScheduler == existingScheduler) {
                    throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " +
                            "in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!");
                }
                if (!this.exposeSchedulerInRepository) {
                    // Need to remove it in this case, since Quartz shares the Scheduler instance by default!
                    SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName());
                }
                return newScheduler;
            }
        }
        finally {
            if (overrideClassLoader) {
                // Reset original thread context ClassLoader.
                currentThread.setContextClassLoader(threadContextClassLoader);
            }
        }
    }

創(chuàng)建的過程和原生的quartz的邏輯是一樣的了邦鲫,都是先從緩存中獲取,如果緩存中沒有神汹,再調(diào)用getScheduler方法獲取庆捺。

所以Scheduler的創(chuàng)建spring boot只是包裝了一層殼,最后的邏輯和quartz原生是一樣的屁魏。那么job和trigger是什么時候注入進去的呢滔以?afterPropertiesSet方法還只分析了一個方法,還有兩個方法

registerListeners();
registerJobsAndTriggers();

見名知意氓拼,這兩個方法就是注冊監(jiān)聽器你画、job和detail的。注入的邏輯和原生沒什么區(qū)別桃漾,這里就不再闡述了坏匪。

總結(jié)

這篇筆記分析了quartz原生的初始化過程和spring boot quartz初始化過程。spring boot初始化bean基本都是包裝了一層AutoConfiguration需要的bean撬统,底層實現(xiàn)還是和原生的一樣剥槐。還介紹了閱讀源碼的技術(shù),去除干擾宪摧,帶著目標(biāo)去找代碼的實現(xiàn)粒竖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颅崩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蕊苗,更是在濱河造成了極大的恐慌沿后,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朽砰,死亡現(xiàn)場離奇詭異尖滚,居然都是意外死亡,警方通過查閱死者的電腦和手機瞧柔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門漆弄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人造锅,你說我怎么就攤上這事撼唾。” “怎么了哥蔚?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵倒谷,是天一觀的道長。 經(jīng)常有香客問我糙箍,道長渤愁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任深夯,我火速辦了婚禮抖格,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咕晋。我一直安慰自己他挎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布捡需。 她就那樣靜靜地躺著,像睡著了一般筹淫。 火紅的嫁衣襯著肌膚如雪站辉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天损姜,我揣著相機與錄音饰剥,去河邊找鬼。 笑死摧阅,一個胖子當(dāng)著我的面吹牛汰蓉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棒卷,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼顾孽,長吁一口氣:“原來是場噩夢啊……” “哼祝钢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起若厚,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拦英,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后测秸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疤估,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年霎冯,在試婚紗的時候發(fā)現(xiàn)自己被綠了铃拇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡沈撞,死狀恐怖慷荔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情关串,我是刑警寧澤拧廊,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站晋修,受9級特大地震影響吧碾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜墓卦,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一倦春、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧落剪,春花似錦睁本、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凡泣,卻和暖如春枉疼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞋拟。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工骂维, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贺纲。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓航闺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子潦刃,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

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