Quartz與Spring集成—— SchedulerFactoryBean的初始化分析

分享知識 傳遞快樂

 

前言

Quartz是一個開源的定時調度框架,支持集羣部署。我們可以通過其Java API來使用它,或者通過Spring來配置與管理,也可以結合使用兩種方式。本文重點分析Quartz2.2.3與Spring4.3.0.RELEASE集成時的初始化過程。

SchedulerFactoryBean與Spring集成時通常需要在Spring配置文件中加入SchedulerFactoryBean這個工廠Bean,例如:

<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="overwriteExistingJobs" value="true"/>
        <property name="configLocation" value="classpath:quartz.properties"/>
    </bean>

再來看看SchedulerFactoryBean的類定義:

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

}


從中看到其實現了FactoryBean、BeanNameAware、ApplicationContextAware、InitializingBean、DisposableBean等常用接口,這些接口的具體意義本文不作贅述,不瞭解的可以專門研究下Spring的原理和源碼實現。

根據Spring的原理我們知道,如果Bean本身實現了InitializingBean接口,那麼在Spring加載解析BeanDefinition,並初始化Bean後會調用SchedulerFactoryBean的afterPropertiesSet方法,這裏只會挑出其中的關鍵代碼進行分析。

初始化SchedulerFactory,在afterPropertiesSet中首先會初始化SchedulerFactory,代碼如下:

 // Create SchedulerFactory instance...
SchedulerFactory schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
        initSchedulerFactory(schedulerFactory);

屬性schedulerFactoryClass的默認值是StdSchedulerFactory.class,因此這裏默認會初始化StdSchedulerFactory,用戶也可以使用Spring的配置文件修改schedulerFactoryClass的值爲其他SchedulerFactory接口的實現(比如RemoteScheduler或者繼承RemoteMBeanScheduler的子類)。在使用Spring的BeanUtils工具類對SchedulerFactory實例化後,調用initSchedulerFactory方法(見代碼清單1)對SchedulerFactory初始化。

代碼清單1

初始化SchedulerFactory

private void initSchedulerFactory(SchedulerFactory schedulerFactory) throws SchedulerException, IOException {
        if (!(schedulerFactory instanceof StdSchedulerFactory)) {
            if (this.configLocation != null || this.quartzProperties != null ||
                    this.taskExecutor != null || this.dataSource != null) {
                throw new IllegalArgumentException(
                        "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
            }
            // Otherwise assume that no initialization is necessary...
            return;
        }
 
        Properties mergedProps = new Properties();
 
        if (this.resourceLoader != null) {
            mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS,
                    ResourceLoaderClassLoadHelper.class.getName());
        }
 
        if (this.taskExecutor != null) {
            mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS,
                    LocalTaskExecutorThreadPool.class.getName());
        }
        else {
            // Set necessary default properties here, as Quartz will not apply
            // its default configuration when explicitly given properties.
            mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());
            mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT));
        }
 
        if (this.configLocation != null) {
            if (logger.isInfoEnabled()) {
                logger.info("Loading Quartz config from [" + this.configLocation + "]");
            }
            PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation);
        }
 
        CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);
 
        if (this.dataSource != null) {
            mergedProps.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());
        }
 
        // Make sure to set the scheduler name as configured in the Spring configuration.
        if (this.schedulerName != null) {
            mergedProps.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName);
        }
 
        ((StdSchedulerFactory) schedulerFactory).initialize(mergedProps);
    }


仔細閱讀initSchedulerFactory方法,可以理解其初始化過程如下:

  • 對於非StdSchedulerFactory的其他SchedulerFactory,需要對參數進行檢查;
  • 設置內置的屬性並存入mergedProps這個字典中。這些屬性包括:
  • org.quartz.scheduler.classLoadHelper.class:用於Quartz與Spring集成時加載Spring資源;
  • org.quartz.threadPool.class:執行Quartz中Task的線程池;
  • org.quartz.threadPool.threadCount:執行Quartz中Task的線程池的線程數量。

加載configLocation屬性指定的屬性文件中的屬性併合併到mergedProps中,這說明屬性文件中的配置可以覆蓋內置的屬性參數。向mergedProps中設置其它屬性:

  • org.quartz.jobStore.class:作業持久化存儲的類,值爲LocalDataSourceJobStore;
  • org.quartz.scheduler.instanceName:值爲Spring配置文件中設置的值;
  • 調用StdSchedulerFactory的initialize方法進一步初始化,實質上不過是創建PropertiesParser對mergedProps進行包裝(見代碼清單2);

代碼清單2

StdSchedulerFactory的initialize實現

 public void initialize(Properties props) throws SchedulerException {
        if (propSrc == null) {
            propSrc = "an externally provided properties instance.";
        }
 
        this.cfg = new PropertiesParser(props);
    }


初始化後的動作在SchedulerFactory初始化完成後,還會執行代碼清單3中代碼,其步驟歸納如下:

  • 使用ThreadLocal技術持有resourceLoader、taskExecutor、dataSource、nonTransactionalDataSource;
  • 調用createScheduler方法創建調度器(具體內容請閱讀《Quartz與Spring集成——創建調度器》一文);
  • 調用populateSchedulerContext,指定調度上下文(SchedulerContext)的屬性和它有的Spring的ApplicationContext;
  • 給調度器設置作業工廠類JobFactory;
  • 調用registerListeners方法註冊有關調度、作業、觸發器等內容的監聽器(見代碼清單4);
  • 調用registerJobsAndTriggers方法註冊作業和觸發器(具體內容將會在另一篇博文單獨介紹);

代碼清單3

初始化後的動作

 this.scheduler = createScheduler(schedulerFactory, this.schedulerName);
    populateSchedulerContext();
 
    if (!this.jobFactorySet && !(this.scheduler instanceof RemoteScheduler)) {
        // Use AdaptableJobFactory as default for a local Scheduler, unless when
        // explicitly given a null value through the "jobFactory" bean property.
        this.jobFactory = new AdaptableJobFactory();
    }
    if (this.jobFactory != null) {
        if (this.jobFactory instanceof SchedulerContextAware) {
            ((SchedulerContextAware) this.jobFactory).setSchedulerContext(this.scheduler.getContext());
        }
        this.scheduler.setJobFactory(this.jobFactory);
    }
     //省略次要代碼
    registerListeners();
    registerJobsAndTriggers();

代碼清單4

註冊監聽器

 protected void registerListeners() throws SchedulerException {
        ListenerManager listenerManager = getScheduler().getListenerManager();
        if (this.schedulerListeners != null) {
            for (SchedulerListener listener : this.schedulerListeners) {
                listenerManager.addSchedulerListener(listener);
            }
        }
        if (this.globalJobListeners != null) {
            for (JobListener listener : this.globalJobListeners) {
                listenerManager.addJobListener(listener);
            }
        }
        if (this.globalTriggerListeners != null) {
            for (TriggerListener listener : this.globalTriggerListeners) {
                listenerManager.addTriggerListener(listener);
            }
        }
    }

總結

對於熟悉Java的開發人員而言,任何Java對象(基本對象和複合對象)在使用之前都需要初始化,Quartz作爲一個大的組件,其本身也是一個對象。從SchedulerFactoryBean的類定義中,我們可以看到其充分利用了Spring提供的各種擴展接口,以便於在調度上下文中使用Spring支持的豐富功能。在SchedulerFactory的初始化過程中,我們看到SchedulerFactoryBean支持多種注入屬性,而且這些屬性可以覆蓋內置的屬性設置,使用者可以根據自身需要進行配置。另外通過configLocation屬性指定屬性文件,可以在單獨的屬性文件中配置屬性,當要配置的屬性很多時,可以避免xml配置臃腫。添加對調度、作業及觸發器等內容的監聽器添加,以便於感興趣的組件,在以上內容發生變化時,進行一些操作。這種方式也能夠將其他組件與SchedulerFactoryBean之間的關係進行解耦。

 

 

 

 

 

——————————————————————

由於寫做時間匆忙,如不妥之處請海涵,留言指正。

相互學習,共同進步

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章