研究源碼金砍,從簡單使用開始局蚀,跑一遍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)粒竖。