Trigger:Quartz的触发器,任务需要绑定到Trigger上,最终被Trigger的执行规则触发,才能被调用执行。JobStore是Quartz用来保存作业执行现场的一个组件。我们知道JobDetail和Trigger必须先注册并绑定到任务调度器,然后才能最终执行Job。这个注册动作实际上是将JobDetail和Trigger发送到JobStore进行存储。JobStore是JobDetail和Trigger的容器。SimpleTrigger和CronTriggerQuartz实现了两个比较重要和常用的触发器:SimpleTrigger和CronTrigger。SimpleTrigger是一个简单的触发器,可以实现比较简单的触发规则,比如什么时候开始执行,多久执行一次,执行多少次,什么时候结束任务等等。CronTrigger是一个支持cron表达式的触发器。由于它可以支持cron表达式,因此CronTrigger可以支持非常复杂的触发规则,例如一天中的什么时间、一周中的什么时间、一个月中的什么时间等等。SimpleTrigger和CronTrigger都可以使用Calendar支持来避免在特定时间点触发任务,比如周六日不触发,节假日不触发等。Trigger实现的底层原理我们从以下两个角度来理解Trigger实现的底层原理:Trigger注册(绑定)和JobDetail,最后通过QuartzScheduler的scheduleJob方法完成绑定。看一下源码:publicDatescheduleJob(JobDetailjobDetail,Triggertrigger)throwsSchedulerException{validateState();if(jobDetail==null){thrownewSchedulerException("JobDetailcannotbenull");}//省略代码resources.getJobStore().storeJobAndTrigger(jobDetail,trig);notifySchedulerListenersJobAdded(jobDetail);notifySchedulerThread(trigger.getNextFireTime().getTime());扳机);//省略代码,最后调用JobStore的storeJobAndTrigger方法完成注册绑定。Quartz可以支持只存储在内存中的JobStore:RAMJobStore,或者持久化到数据库的JobStore:JobStoreSupport。今天先研究一下RAMJobStore,以后再学习其他类型的JobStore。RAMJobStore#JobDetailregistrationTrigger和JobDetail在RAMJobStore中绑定注册。JobDetail将克隆的实现对象(JobWrapper)包装后注册到RAMJobStore中,并存储在两个容器中:jobsByKey和jobsByGroup容器。而且,不允许重复注册同一个JobDetail。我们在上一篇文章中说过,JobDetail是通过JobKey作为键值来唯一标识的。注册JobDetail时,如果容器jobsByKey中已经存在另一个具有相同JobKey的JobDetail,则要么替换它,要么抛出异常,这取决于注册时传入的参数是否允许替换。Trigger与JobDetail的绑定在注册之前,Tigger需要先绑定JobDetail。两者的绑定是通过给Trigger设置JobKey来实现的。Trigger可以通过两种方式绑定JobDetail:创建Triiger时:通过任务调度器的scheduleJob方法(需要JobDetail和Trigger参数)通过forJob指定绑定到Trigger的JobDetail来绑定RAMJobStore#Trigger注册类似于JobDetail,并且Trigger也会在RAMJobStore中注册克隆的包装器对象(TriggerWrapper)。与JobDetail类似,Trigger包含一个TriggerKey作为Trigger的唯一键值。JobStore不允许注册键值重复的触发器。Trigger会被注册到RAMJobStore的以下容器中:triggersByKey:HashMapwithTriggerKeyaskey/valueasTrigger。triggersByJob:以Trigger的JobKey为key的HashMap。因为同一个JobDetail可以绑定不同的Trigger,triggersByJob的值是由JobDetail对应的Triggers组成的ListtriggersByGroup:key是Trigger的group/value。Triggers组成的HashMap的HashMapTimeTriggers:允许被触发的Trigger组成的TreeSetBlockedJobs:阻塞的Job组成的HashSet需要简单说明一下timeTriggers和blockedJobs。当一个trigger注册到RAMJobStore时,如果trigger绑定的Job不在blockedJobs中的话,会直接添加到timeTriggers中等待触发。否则,如果在blockedJobs中,则不会加入到timeTriggers中,所以暂时不会触发,trigger的状态也会设置为blocked。如果Job实现类被@DisallowConcurrentExecution注解,则该实现类通过一定的jobKey绑定到RAMJobStore中的Trigger上。如果有多个Trigger,则不允许这些Trigger并发。为了实现上面不允许并发的控制,当一个job被trigger触发时,Quartz会从timeTriggers中移除该job的所有其他trigger,并将该job放入blockedJobs中。没有上述控制要求的触发器在注册后放在timeTriggers中等待触发。触发器触发作业进程。Trigger无疑应该在作业调度线程QuartzSchedulerThread的run方法中触发。从源码中可以发现,触发逻辑是:从作业执行线程池中获取availThreadCount,即当前可用线程数,调用JobStore的acquireNextTriggers。方法,获取特定短时间(idleWaitTime)内可能需要触发的trigger,不超过availThreadCount的trigger数调用JobStore的triggersFired方法对获取到的可能需要触发的trigger进行二次处理,以及得到最终的pendingtrigger结果集,循环处理最终pendingtrigger结果集中需要触发的每一个trigger。使用JobRunShell包装触发器并将其发送到线程池以执行与触发器关联的作业。我们来看看上面两种RAMJobStore的实现方法。RAMJobStore#acquireNextTriggers获取需要在idleWaitTime(默认30秒)内触发的触发器。我们知道Trigger有一个重要的属性nextFireTime,用来记录触发器的下一次触发时间。每次触发触发器并执行作业时,都会根据触发器的规则重新计算下一次触发时间。nextFireTime==null表示触发器将不再被调用,Quartz将从队列中移除触发器。RAMJobStore的acquireNextTriggers方法从timeTriggers中获取每一个Trigger进行判断,如果符合规则(应该在idleWaitTime触发),则添加到要触发的结果集中返回。如果trigger的下一次触发时间在idleWaitTime之后,则trigger这次不需要处理。否则,应将触发器添加到要触发的结果集中。但是仍然需要检查当前触发器绑定的作业是否设置了@DisallowConcurrentExecution。如果设置为不允许并发,则只有一个与该作业关联的触发器将被添加到结果集中被触发。否则,如果没有设置@DisallowConcurrentExecution,则直接将当前触发器添加到要触发的结果集中。添加到待定结果集的触发器将同时从timeTriggers中删除。最后返回获取到的结果集进行触发。RAMJobStore#triggersFired检查结果集中的触发器一一触发。为了安全起见,再次从timeTriggers中删除当前触发器。调用Trigger的触发方法。该方法的主要作用是重新计算触发器的nextFireTime。使用当前触发器及其绑定作业创建一个TriggerFiredBundle。如果触发器绑定作业设置了@DisallowConcurrentExecution,将该作业的所有触发器移出timeTriggers并将该作业添加到blockedJobs。否则,如果重新计算后的nextFireTime不为空,说明当前trigger还会有触发要求(比如每天下午2点触发,则trigger的nextFireTime应该是次日下午2点)),然后将触发器添加到timeTriggers并等待下一个触发器。上面的逻辑就是重复执行触发器可以重复执行的原因。将组装好的TriggerFiredBundle添加到最终结果集中触发返回。最终要处理的触发器结果集返回给QuartzSchedulerThread的run方法后,就会被调用执行。至此,我们分析了作业??从配置到执行的逻辑。待处理队列中触发器的顺序timeTriggers是待处理触发器队列,其数据结构为TreeSet。我们知道TreeSet是有序的,那么放入的触发器也应该是有序的。其实我们不难想到这个顺序,就是按照下次执行的时间和优先级排序的。Quartz在TriggerWrapperComparator中实现了有序队列的逻辑。TriggerWrapperComparator充当timeTriggers的比较器,并在初始化timeTriggers时创建。比较器TriggerWrapperComparator的compare方法:最后调用TriggerTimeComparator的compare方法。从类名我们也可以发现应该是按时间排序的。方法源码很简单,就是先按nextFireTime排序,再按priority排序。多于!PreviousQuartz-Job&JobDetailNextQuartz-Misfire
