最近项目中用到了Quartz框架,作为定时任务的调度框架,无论是和Spring的完美融合还是直接使用Java代码来进行使用,可以说都非常的简单且稳定,某天突然想到如果没有Quartz框架,我们应该如何实现一个纯Java代码的定时任务调度框架呢?
于是我就忍不住去看了Quartz的源码(多捞啊…),然后发现Quartz的核心设计思路竟然和我第二版的想法差不多,那么废话不多数,亮出源码吧!
可以说Quartz从任务的提交,到任务的定时执行,就是依靠这三件套来完成的,当然这里我假设你已经掌握了Quartz的基本用法,下面我们对这些组件一一分析。
我们知道,在想要在Quartz中提交一个任务,我们需要有一个自己实现Job
接口的类,只需要将定时触发的代码覆写在其execute
方法中。
// 实现Job接口的类
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("this is a test job!");
}
}
使用Quartz的时候,我们通过实现Job接口来完成我们需要的业务,execute
方法是需要Job
接口需要实现的唯一方法。
实际上,Quartz内部需要将Job包装的更为复杂,就像Spring
会在内部包装生成Bean
一样,Job
最终会被包装成为一个JobDetailImpl
对象。
JobDetailImpl
是JobDetail
接口的默认实现类,我们可以看其中一些属性。
JobDetailImpl
对象是给Quartz框架使用的,就像SpringBean
对象是给Spring框架使用的,额外在Job
基础上进行封装。
关于JobDetail
对象的生成,Quartz使用了建造者模式:
可以看到JobBuilder
类的结构,很明显这是一个Builder
类。
public JobDetail build() {
JobDetailImpl job = new JobDetailImpl();
job.setJobClass(jobClass);
job.setDescription(description);
if(key == null)
key = new JobKey(Key.createUniqueName(null), null);
job.setKey(key);
job.setDurability(durability);
job.setRequestsRecovery(shouldRecover);
if(!jobDataMap.isEmpty())
job.setJobDataMap(jobDataMap);
return job;
}
**核心方法如上,Builder模式在安卓端比较常用,这里的好处就是,可以比较方便的生成一个JobDetail
对象,交给Scheduler
使用。**ps:关于建造者模式-菜鸟教程
Tigger
在Quartz作为一个触发器的角色,我们可以通过Trigger
对象来实现任务调度的频率设置,时间设置等。
Trigger
在Quartz中有两种常用的使用手段,一种是基于SimpleTrigger
接口实现的简单触发器,这种触发器规则比较简单,一般都是间隔特定时间执行,还有一个字是基于CronTrigger
接口实现的Cron表达式触发器,Cron表达式能实现更为强大的触发规则,用过Quartz的小伙伴一定会接触到Cron表达式(使用Linux肯定也接触过)。这里我们看更为简单的SimpleTrigger
Trigger
实例和JobDetail
实例一样,都是采用了Builder模式创造,真正的实现类是SimpleTriggerImpl
类。
我们先来看一下SimpleTrigger
类的继承关系图:
其实关于Trigger作用的说明,我想Quartz自带的JavaDoc应该比我表述的清楚:
/**
* A {@link Trigger}
that is used to fire a Job
* at a given moment in time, and optionally repeated at a specified interval.
*
* `SimpleTrigger`类开头的这段注释很好的说明了`SimpleTrigger`的作用,fire这里意为执行。
* /
SimpleTriggerImpl
类里面有很多属性,包括Trigger名,属组,开始时间,执行次数,执行间隔等等很多属性,和JobDetailImpl
类一样,这些属性都是给Quartz框架用的。
这里还要特别说明一个和Trigger
紧密相关的类,就是ScheduleBuilder
类,很明显这也是一个Builder模式的类,其实就是为了生成一个Trigger
使用的时间表,然后作为参数传入TriggerBuilder
类中,这里注释说的也很明白,ScheduleBuilder
会被用于定义Trigger的触发时间。(当然CronTrigger
又是另一套转换逻辑,需要把corn表达式转换成时间表)
下面进入一个经典问题:我现在有了咖啡,有了牛奶,请问我怎么得到咖啡牛奶?
好吧只需要一个杯子。
就像现在我们有了Job
有了Trigger
,怎么把他们建立对应关系并储存起来呢?答案就是JobStore
类,JobStore
是建立JobDetail
和Trigger
关联的关键类,同时也存储JobDetail
和Tigger
的类。Quartz内部有两个实现类:
RAMJobStore
,这个存储类的特性是项目重启以后会清空数据。JobStoreTX
类和JobStoreCMT
类,这两个类分别对应Quartz单机持久化部署和集群持久化部署的数据库操作实现。这里我们分析RAMJobStore
,这个类我们不需要手动创建,如果配置文件中不启用数据库配置的话,Quartz默认使用这个类。
在Quartz框架中,我们会使用scheduleJob
方法提交Job
和Trigger
,实现Job
和Trigger
关联的关键步骤有两个
Trigger
实例设置JobKey
,这里需要注意Quartz框架中每个Job
都会有一个唯一的JobKey
来标识JobDetial
和Trigger
到JobStore
中,Quartz中有两种方式存储,一种时存储到内存中,一种是使用JDBC
存储到数据库中。
下面是RAMJobStore
类的核心方法,storeJobAndTrigger
源码:
public void storeJobAndTrigger(JobDetail newJob,
OperableTrigger newTrigger) throws JobPersistenceException {
storeJob(newJob, false);
storeTrigger(newTrigger, false);
}
简单明了,先存Job
再存Trigger
,当然其实storeJob
和storeTrigger
方法才是真正的实现了存储对象方法。
下面问题来了, 如果让你设计一种存储Job
和Trigger
在内存中的结构,同时让他具有关联性,你会选择Java中哪种容器实现?让我想起来大二我们课程设计实现一个SQL型数据库,我硬生生用几层嵌套HashMap
创造了一种存储结构。当然这里RAMJobStore
中就是采用了嵌套HashMap
来实现Job
和Trigger
的映射关系。
// 核心存储容器
protected Map<JobKey, List<TriggerWrapper>> triggersByJob =
new HashMap<JobKey, List<TriggerWrapper>>(1000);
没错,就是你想象的这么简单。JobKey
之前我们已经说过了,是Quartz框架中全局唯一的(即使分布式部署Quartz也是全局唯一的,用到了UUID
类),List
是Trigger
的列表,也就是说一个Job
可以对应多个Trigger
触发器。
我们再来看一下存储方法:
public void storeTrigger(OperableTrigger newTrigger,
boolean replaceExisting) throws JobPersistenceException {
TriggerWrapper tw = new TriggerWrapper((OperableTrigger)newTrigger.clone());
synchronized (lock) {
// …… 省略部分代码
// 核心功能代码:add to triggers by job
List<TriggerWrapper> jobList = triggersByJob.get(tw.jobKey);
if(jobList == null) {
jobList = new ArrayList<TriggerWrapper>(1);
triggersByJob.put(tw.jobKey, jobList);
}
jobList.add(tw);
// ……省略部分代码
}
}
synchronized
关键字在这里是确保多线程环境下的数据安全,其实真的很简单,就是单纯的去存储数据,不过JobStore
在我们之后要讲到的Scheduler
核心代码里扮演着很重要的角色。
到现在为止,我们还没有分析到Quartz的核心工作代码,只是了解了Job
和Trigger
的创建,包装和存储,当然这也是非常重要的,俗话说巧妇难为无米之炊,Job
和Trigger
作为原材料也是Quartz必不可少的一环,下篇我们会去分析Scheduler
源码,也就是Quartz的核心代码,其中涉及到的线程模型,设计思想,会进行一些分析。
关于我们的一个想法,Quartz是如何将它照进现实的,还请看下一篇。
参考资料: