1. 程式人生 > >Quartz與Spring整合—— SchedulerFactoryBean的初始化分析

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方法,可以理解其初始化過程如下:

  1. 對於非StdSchedulerFactory的其他SchedulerFactory,需要對引數進行檢查;
  2. 設定內建的屬性並存入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中程式碼,其步驟歸納如下:

  1. 使用ThreadLocal技術持有resourceLoader、taskExecutor、dataSource、nonTransactionalDataSource;
  2. 呼叫createScheduler方法建立排程器(具體內容請閱讀《Quartz與Spring整合——建立排程器》一文);
  3. 呼叫populateSchedulerContext,指定排程上下文(SchedulerContext)的屬性和它有的Spring的ApplicationContext;
  4. 給排程器設定作業工廠類JobFactory;
  5. 呼叫registerListeners方法註冊有關排程、作業、觸發器等內容的監聽器(見程式碼清單4);
  6. 呼叫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之間的關係進行解耦。

後記:個人總結整理的《深入理解Spark:核心思想與原始碼分析》一書現在已經正式出版上市,目前京東、噹噹、天貓等網站均有銷售,歡迎感興趣的同學購買。