quart初始化流程
研究源码,从简单使用开始,跑一遍demo后,再研究是如何初始化的,我们先研究以下的代码:
public class QuartzSimpleDemo {
public static void main(String[] args) throws SchedulerException {
//1\. 创建Scheduler
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler sched = sfact.getScheduler();
//2\. 创建job信息
JobDetail testTaskJob = JobBuilder.newJob(TestTaskJob.class).storeDurably()
.withIdentity("TestTaskJob").build();
//3\. 创建触发器
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. 创建Scheduler
SchedulerFactory sfact = new StdSchedulerFactory();
工厂模式创建一个Scheduler工厂,quartz中有两个实现类,DirectSchedulerFactory和StdSchedulerFactory,常用StdSchedulerFactory类。
Scheduler sched = sfact.getScheduler();
从工厂中获取一个Scheduler对象,这个是我们研究的重点。我们看下getSchedulerd的逻辑
public Scheduler getScheduler() throws SchedulerException {
//初始化配置文件
if (cfg == null) {
initialize();
}
SchedulerRepository schedRep = SchedulerRepository.getInstance();
//根据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 初始化配置
-
如果创建SchedulerFactory时,传了配置文件,则使用传入的
-
如果创建SchedulerFactory时没有传,则看系统参数中配置了org.quartz.properties,也就-Dorg.quartz.properties是否配置。
-
如果系统参数没有传,或者传了找不到,则在各种路径中查找quartz.properties文件。所以建议直接在配置文件中新增quartz.properties文件就可以。
1.2 缓存中加载Scheduler
上面代码是从SchedulerRepository获取缓存的Scheduler,SchedulerRepository中有一个全局的HashMap,key是Scheduler名,值是Scheduler对象,开始时肯定是空,所以调用instantiate方法.
1.3 初始化
如果缓存中没有,则创建 Scheduler对象,看下创建逻辑,创建逻辑主要是读取配置,根据配置创建对应的类.这个方法有点长,不过注释写的好,很容易看懂,我留下主要的代码
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;
}
}
上面的代码,我把代码都删了,留下注释和几行核心的代码,看起来更清晰。主要逻辑是根据配置创建不同的类。然后把所有的类都封装到QuartzSchedulerResources对象中。然后再创建QuartzScheduler对象。Scheduler大多数操作都是依赖QuartzScheduler完成的。最后创建Scheduler对象,并把Scheduler对象存到缓存中。
QuartzScheduler创建时启动了生产者线程,到研究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. 创建JobDetail
利用构造者模式创建JobDetail对象,比较简单
3. 创建Trigger
利用构造者模式创建Trigger对象,比较简单。
4. 调度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调度,开始参数校验,然后存储job和trigger,然后通知各种监听器。
5. 启动
启动主要也是通知监听事情,表示Scheduler启动起来了。
spring boot quartz初始化
1. 查找配置类
spring boot quartz的使用我们并没有使用任何注解就能够使用,那它是如何初始化的呢?因为使用了spring boot,所以我猜测使用了spring boot的自动配置功能,所以我用IDEA搜索了QuartzAutoConfiguration,还真让我搜到了。如果不知道spring boot的加载过程,可以查看SchedulerFactory#getScheduler被哪里调用了,反推初始化过程。
@Configuration
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class })
@EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class QuartzAutoConfiguration {}
QuartzAutoConfiguration使用了@Configuration注解,在spring boot启动的时候就会加载,当系统中有Scheduler相关的类时,就会创建对应的bean。
2. 构造函数
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;
}
构造函数中注入了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,设置了配置、jobDetail和trigger属性值。接下来看下SchedulerFactoryBean的初始化过程。
public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>,
BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
...
}
SchedulerFactoryBean实现了InitializingBean和FactoryBean,所以在初始化的时候会调用afterPropertiesSet方法,在得到bena的时候会调用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的注释,所以应该就是在prepareScheduler中初始化scheduler对象的。在创建scheduler之前,先创建了SchedulerFactory,看下创建过程。
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创建的时候并没有给schedulerFactory属性赋值,所以schedulerFactory是空的,如果是空的则加载schedulerFactoryClass类,schedulerFactoryClass的值是StdSchedulerFactory。这和我们直接使用quartz是一样的。接下来就是创建Scheduler对象。
private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException {
...
// Get Scheduler instance from SchedulerFactory.
try {
Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName);
...
return scheduler;
}
prepareScheduler方法又调用了createScheduler,继续跟踪
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);
}
}
}
创建的过程和原生的quartz的逻辑是一样的了,都是先从缓存中获取,如果缓存中没有,再调用getScheduler方法获取。
所以Scheduler的创建spring boot只是包装了一层壳,最后的逻辑和quartz原生是一样的。那么job和trigger是什么时候注入进去的呢?afterPropertiesSet方法还只分析了一个方法,还有两个方法
registerListeners();
registerJobsAndTriggers();
见名知意,这两个方法就是注册监听器、job和detail的。注入的逻辑和原生没什么区别,这里就不再阐述了。
总结
这篇笔记分析了quartz原生的初始化过程和spring boot quartz初始化过程。spring boot初始化bean基本都是包装了一层AutoConfiguration需要的bean,底层实现还是和原生的一样。还介绍了阅读源码的技术,去除干扰,带着目标去找代码的实现。