打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
quartz中JobExecutionContext的使用

一个系统里面经常需要做一些定时任务,比如说定时清空今日得分,或者定时清理临时文件。简单的定时任务很容易实现,用线程或者用Timer就可以了,但是始终需要自己写大量代码才能实现复杂的需求。

于是便有Quartz。不过,Quartz太久没有更新了,而且它太复杂。由于我的系统是基于Spring构建的,所以我希望能使用Spring支持的scheduling类库,可惜Spring只支持commonj和Quartz,正确来说,在Java界,并没有别的scheduling类库了,而commonj只是一个interface,没有具体的实现,似乎在Weblogic之类的里面有实现。

当然,也有另外一个选择,也是轻量级的脚本语言常用的做法,就是使用Linux的crontable,可以实现比较复杂的定时。不过,脚本语言调用数据库并不是很方便(应该说我们的团队技术累积上的问题),如果用crontable启动Java,每次启动的成本又比较高。

在评估过各种方案之后,我还是选择了使用Quartz,首先从Spring的辅助类开始入手吧。

题外话,在一个集群的环境里面(也就是多个Tomcat的环境下),定时任务应该是独立的应用,也就是不应该在每一个Tomcat里面都启动Quartz或者定时线程。另外,在Tomcat的应用里面,也是尽量不要使用线程,有可能一点点小错误就会导致整个Tomcat崩溃(其实我们还是使用很多的,呵呵)。

根据Quartz的使用行为,一个任务我们至少需要一个Job、一个JobDetail、一个Trigger(真复杂)

  1. JobDetail jobDetail <span style="color: rgb(51, 153, 51);">=</span> <span style="font-weight: bold; color: rgb(0, 0, 0);">new</span> JobDetail<span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">"myJob"</span>,               <span style="color: rgb(102, 102, 102); font-style: italic;">// job name</span>  
  2.                                   sched.<span style="color: rgb(0, 102, 51);">DEFAULT_GROUP</span>,   <span style="color: rgb(102, 102, 102); font-style: italic;">// job group </span>  
  3.                                   DumbJob.<span style="font-weight: bold; color: rgb(0, 0, 0);">class</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span><span style="color: rgb(102, 102, 102); font-style: italic;">// the java class to execute</span>  
  4. Trigger trigger <span style="color: rgb(51, 153, 51);">=</span> TriggerUtils.<span style="color: rgb(0, 102, 51);">makeDailyTrigger</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(204, 102, 204);">8</span>, <span style="color: rgb(204, 102, 204);">30</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  5. trigger.<span style="color: rgb(0, 102, 51);">setStartTime</span><span style="color: rgb(0, 153, 0);">(</span><span style="font-weight: bold; color: rgb(0, 0, 0);">new</span> <span style="color: rgb(0, 51, 153);">Date</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  6. trigger.<span style="color: rgb(0, 102, 51);">setName</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">"myTrigger"</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  7. sched.<span style="color: rgb(0, 102, 51);">scheduleJob</span><span style="color: rgb(0, 153, 0);">(</span>jobDetail, trigger<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  

首先!!我在这里要明确一个事情。Job类是没有状态的!!
这是什么概念呢,就是说,你实现的一个Job(例如上面的代码的DumbJob),并不是由你自己new出来的,留意一下newJobDetail的代码,传入的参数是DumbJob.class,而不是一个具体的job实例。Quartz帮你吧Jobnew一份出来,并且调用相应的接口,并没有别的功能。

这里会带来一个什么问题呢,我们先来看看Spring的辅助类。

Spring有两个辅助类可以产生JobDetail类,需要留意的是,Spring并不辅助产生Job类,也就是Spring认为Job类不需要管理。

我们先看看第一个,JobDetailBean

  1. <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"><bean</span> <span style="color: rgb(0, 0, 102);">name</span>=<span style="color: rgb(255, 0, 0);">"exampleJob"</span> <span style="color: rgb(0, 0, 102);">class</span>=<span style="color: rgb(255, 0, 0);">"org.springframework.scheduling.quartz.JobDetailBean"</span><span style="font-weight: bold; color: black;">></span></span>  
  2.   <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"><property</span> <span style="color: rgb(0, 0, 102);">name</span>=<span style="color: rgb(255, 0, 0);">"jobClass"</span> <span style="color: rgb(0, 0, 102);">value</span>=<span style="color: rgb(255, 0, 0);">"example.ExampleJob"</span> <span style="font-weight: bold; color: black;">/></span></span>  
  3.   <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"><property</span> <span style="color: rgb(0, 0, 102);">name</span>=<span style="color: rgb(255, 0, 0);">"jobDataAsMap"</span><span style="font-weight: bold; color: black;">></span></span>  
  4.     <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"><map<span style="font-weight: bold; color: black;">></span></span></span>  
  5.       <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"><entry</span> <span style="color: rgb(0, 0, 102);">key</span>=<span style="color: rgb(255, 0, 0);">"timeout"</span> <span style="color: rgb(0, 0, 102);">value</span>=<span style="color: rgb(255, 0, 0);">"5"</span> <span style="font-weight: bold; color: black;">/></span></span>  
  6.     <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"></map<span style="font-weight: bold; color: black;">></span></span></span>  
  7.   <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"></property<span style="font-weight: bold; color: black;">></span></span></span>  
  8. <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"></bean<span style="font-weight: bold; color: black;">></span></span></span>  

不知道大家有没有看出问题在哪里。propertyjobClass是一个类名,并不是一个实例名!也就是跟Quartz的调用一样,是Quartz负责帮你new一个example.ExampleJob类出来,也就是说你不能对Job类进行任何形式的注入(IOC),比如说,我们的example.ExampleJob是一个DAO,需要传入DataSource进行DB操作,没辙。

因此,Spring提供了另外一个JobDetail辅助类MethodInvokingJobDetailFactoryBean

  1. <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"><bean</span> <span style="color: rgb(0, 0, 102);">id</span>=<span style="color: rgb(255, 0, 0);">"jobDetail"</span> <span style="color: rgb(0, 0, 102);">class</span>=<span style="color: rgb(255, 0, 0);">"org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"</span><span style="font-weight: bold; color: black;">></span></span>  
  2.   <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"><property</span> <span style="color: rgb(0, 0, 102);">name</span>=<span style="color: rgb(255, 0, 0);">"targetObject"</span> <span style="color: rgb(0, 0, 102);">ref</span>=<span style="color: rgb(255, 0, 0);">"exampleBusinessObject"</span> <span style="font-weight: bold; color: black;">/></span></span>  
  3.   <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"><property</span> <span style="color: rgb(0, 0, 102);">name</span>=<span style="color: rgb(255, 0, 0);">"targetMethod"</span> <span style="color: rgb(0, 0, 102);">value</span>=<span style="color: rgb(255, 0, 0);">"doIt"</span> <span style="font-weight: bold; color: black;">/></span></span>  
  4. <span style="color: rgb(0, 153, 0);"><span style="font-weight: bold; color: black;"></bean<span style="font-weight: bold; color: black;">></span></span></span>  

你可以留意到,property targetObject是一个ref,指向的是一个常规的Spring管理的Bean。

但是!

MethodInvokingJobDetailFactoryBean很不友好。首先,它是通过反射调用的,而不是Interface,因此我们必须要看了Spring的xml才能知道谁被调用了,你还可能会写一大堆property targetMethod=doIt,而且JobInterface是会传入一个JobExecutionContext,这个被miss了。
其次,如果我们需要大量的Job的话(因为我就是做一个专门用来定时的应用),Spring的配置文件会变得非常臃肿,我希望Job和JobDetail不需要Spring专门管理,只要他是一个Spring管理的Bean,并且实现了Job这个接口就ok了。

这里补充一个事情,我们跳过了Trigger的部分,每一个JobDetail必须配备一个相应的Trigger,因此配置文件是你之前想象中的两倍那么大,而且你还得给每一个Bean命名一个ID,而这个类你以后都不会用到。

我的目标是:
1、只要是实现了Job接口的Spring管理的Bean,自动加入scheduling,根本不用关心JobDetail的存在,也不会有注入的问题
2、所有Job均使用CronTrigger,并且通过配置文件设定Cron Expressions

通过研究MethodInvokingJobDetailFactoryBean和Quartz的代码,我明白到JobDetail是有状态的,而MethodInvokingJobDetailFactoryBean正是利用这点来实现具体效果的,于是便有了我一下这些辅助代码

首先
,需要一个DummyJob,由于Quartz的主入口始终是Job类

  1.   <span style="font-weight: bold; color: rgb(0, 0, 0);">public</span> <span style="font-weight: bold; color: rgb(0, 0, 0);">class</span> DummyJob <span style="font-weight: bold; color: rgb(0, 0, 0);">implements</span> Job <span style="color: rgb(0, 153, 0);">{</span>  
  2. span style="font-weight: bold; color: rgb(0, 0, 0);">public</span> <span style="font-weight: bold; color: rgb(0, 0, 102);">void</span> execute<span style="color: rgb(0, 153, 0);">(</span>JobExecutionContext context<span style="color: rgb(0, 153, 0);">)</span>  
  3.     <span style="font-weight: bold; color: rgb(0, 0, 0);">throws</span> JobExecutionException <span style="color: rgb(0, 153, 0);">{</span>  
  4. Job job <span style="color: rgb(51, 153, 51);">=</span> <span style="color: rgb(0, 153, 0);">(</span>Job<span style="color: rgb(0, 153, 0);">)</span> context.<span style="color: rgb(0, 102, 51);">getMergedJobDataMap</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>.<span style="color: rgb(0, 102, 51);">get</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">"methodInvoker"</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  5. <span style="font-weight: bold; color: rgb(0, 0, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span>job <span style="color: rgb(51, 153, 51);">!=</span> <span style="font-weight: bold; color: rgb(0, 0, 102);">null</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(0, 153, 0);">{</span>  
  6.     job.<span style="color: rgb(0, 102, 51);">execute</span><span style="color: rgb(0, 153, 0);">(</span>context<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  7. <span style="color: rgb(0, 153, 0);">}</span>  
  8. span style="color: rgb(0, 153, 0);">}</span>  
  9.   <span style="color: rgb(0, 153, 0);">}</span>  

jobDataMap就是JobDetail存储状态的地方,DummyJob唯一要做的就是,知道实际的Job类,并且调用它

接下来是戏玉了

  1.         Map<span style="color: rgb(51, 153, 51);"><</span>String, Job<span style="color: rgb(51, 153, 51);">></span> jobMap <span style="color: rgb(51, 153, 51);">=</span> context.<span style="color: rgb(0, 102, 51);">getBeansOfType</span><span style="color: rgb(0, 153, 0);">(</span>Job.<span style="font-weight: bold; color: rgb(0, 0, 0);">class</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  2.         <span style="font-weight: bold; color: rgb(0, 0, 0);">for</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 51, 153);">Map</span>.<span style="color: rgb(0, 102, 51);">Entry</span><span style="color: rgb(51, 153, 51);"><</span>String, Job<span style="color: rgb(51, 153, 51);">></span> entry <span style="color: rgb(51, 153, 51);">:</span> jobMap.<span style="color: rgb(0, 102, 51);">entrySet</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(0, 153, 0);">{</span>  
  3.             <span style="color: rgb(0, 51, 153);">String</span> taskName <span style="color: rgb(51, 153, 51);">=</span> entry.<span style="color: rgb(0, 102, 51);">getKey</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  4.             <span style="color: rgb(0, 51, 153);">String</span> cronExpression <span style="color: rgb(51, 153, 51);">=</span> props.<span style="color: rgb(0, 102, 51);">getProperty</span><span style="color: rgb(0, 153, 0);">(</span>taskName<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  5.             <span style="font-weight: bold; color: rgb(0, 0, 0);">if</span> <span style="color: rgb(0, 153, 0);">(</span>cronExpression <span style="color: rgb(51, 153, 51);">==</span> <span style="font-weight: bold; color: rgb(0, 0, 102);">null</span><span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(0, 153, 0);">{</span>  
  6.                 logger.<span style="color: rgb(0, 102, 51);">warn</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">"[{}] don't have a cronExpression"</span>, taskName<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  7.                 <span style="font-weight: bold; color: rgb(0, 0, 0);">continue</span><span style="color: rgb(51, 153, 51);">;</span>  
  8.             <span style="color: rgb(0, 153, 0);">}</span>  
  9.    
  10.             <span style="font-weight: bold; color: rgb(0, 0, 0);">try</span> <span style="color: rgb(0, 153, 0);">{</span>  
  11.                 Trigger trigger <span style="color: rgb(51, 153, 51);">=</span> <span style="font-weight: bold; color: rgb(0, 0, 0);">new</span> CronTrigger<span style="color: rgb(0, 153, 0);">(</span>taskName <span style="color: rgb(51, 153, 51);">+</span> <span style="color: rgb(0, 0, 255);">"Trigger"</span>, <span style="font-weight: bold; color: rgb(0, 0, 102);">null</span>,  
  12.                         cronExpression<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  13.                 JobDetail jobDetail <span style="color: rgb(51, 153, 51);">=</span> <span style="font-weight: bold; color: rgb(0, 0, 0);">new</span> JobDetail<span style="color: rgb(0, 153, 0);">(</span>taskName <span style="color: rgb(51, 153, 51);">+</span> <span style="color: rgb(0, 0, 255);">"Job"</span>, <span style="font-weight: bold; color: rgb(0, 0, 102);">null</span>,  
  14.                         DummyJob.<span style="font-weight: bold; color: rgb(0, 0, 0);">class</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  15.                 jobDetail.<span style="color: rgb(0, 102, 51);">getJobDataMap</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>  
  16.                         .<span style="color: rgb(0, 102, 51);">put</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">"methodInvoker"</span>, entry.<span style="color: rgb(0, 102, 51);">getValue</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  17.                 scheduler.<span style="color: rgb(0, 102, 51);">scheduleJob</span><span style="color: rgb(0, 153, 0);">(</span>jobDetail, trigger<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  18.             <span style="color: rgb(0, 153, 0);">}</span> <span style="font-weight: bold; color: rgb(0, 0, 0);">catch</span> <span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 51, 153);">ParseException</span> e<span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(0, 153, 0);">{</span>  
  19.                 logger.<span style="color: rgb(0, 102, 51);">error</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">""</span>, e<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  20.             <span style="color: rgb(0, 153, 0);">}</span> <span style="font-weight: bold; color: rgb(0, 0, 0);">catch</span> <span style="color: rgb(0, 153, 0);">(</span>SchedulerException e<span style="color: rgb(0, 153, 0);">)</span> <span style="color: rgb(0, 153, 0);">{</span>  
  21.                 logger.<span style="color: rgb(0, 102, 51);">error</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">""</span>, e<span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  22.             <span style="color: rgb(0, 153, 0);">}</span>  
  23.         <span style="color: rgb(0, 153, 0);">}</span>  

从Spring context里面读取所有实现了Job的类遍历,props是从文件里面读取相应的cronExpression配置。

  1. JobDetail jobDetail <span style="color: rgb(51, 153, 51);">=</span> <span style="font-weight: bold; color: rgb(0, 0, 0);">new</span> JobDetail<span style="color: rgb(0, 153, 0);">(</span>taskName <span style="color: rgb(51, 153, 51);">+</span> <span style="color: rgb(0, 0, 255);">"Job"</span>, <span style="font-weight: bold; color: rgb(0, 0, 102);">null</span>,  
  2.         DummyJob.<span style="font-weight: bold; color: rgb(0, 0, 0);">class</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  
  3. jobDetail.<span style="color: rgb(0, 102, 51);">getJobDataMap</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span>  
  4.         .<span style="color: rgb(0, 102, 51);">put</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 0, 255);">"methodInvoker"</span>, entry.<span style="color: rgb(0, 102, 51);">getValue</span><span style="color: rgb(0, 153, 0);">(</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(0, 153, 0);">)</span><span style="color: rgb(51, 153, 51);">;</span>  

这两句是关键

于是,Quartz变得更sexy了

 

 

 

quartz 中JobExecutionContext的使用

假如execute方法中需要一些额外的数据怎么办?比如说execute
中希望发送一封邮件,但是我需要知道邮件的发送者、接收者等信息?

存在两种解决方案:

1.JobDataMap类:
  每个JobDetail都关联了一个JobDataMap实例,JobDataMap是java.util.Map的子类,基本上是提供key-value形式的数据,并提供了一些便利方法(主要是对java基本数据类型的支持,如put(String key,intvalue)),当开发人员创建JobDetail的时候,可以把附加信息放到JobDataMap中,那么在execute方法中可以根据key找到需要的值。
   JobDetail job = new JobDetail....
  job.getJobDataMap().put("from","snowway@vip.sina.com");
   ...  

在execute中
   String from =jobExecutionContext.getJobDetail().getJobDataMap().getString("from");
  ....

  不过,当你使用数据库存储JobDetail的时候(默认情况下使用RAM),这里有一个致命的弱点,你不能把没有实现java.io.Serializable的对象放入JobDataMap中,因为Quartz将使用Blob字段保存(也可以通过配置文件关闭)序列化过的JobDataMap中的对象。比如你在execute方法中需要一个java.sql.Connection接口实例,这种情况也是普遍的,那么通常情况下你不能把Connection放入JobDataMap,即使你只想在execute中使用。(注:读者可暂时认为上面这段话是正确的,然而可以通过指示quartz改变这种行为,那属于高级话题)

2.假如你需要一个java.sql.Connection,用于在execute中完成某些操作,那么你可以把Connection放入Quartz的SchedulerContext中,execute也可以访问,并且Quartz不会持久化SchedulerContext中的任何东西。

  scheduler.getContext().put("java.sql.Connection",connection); 

execute中
   Connection con =(Connection)jobExecutionContext.getScheduler().getContext().get("java.sql.Connection");
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Quartz.net官方开发指南 第三课:更多关于Jobs和JobDetails - 自由、创新、研究、探索 - 博客园
定时任务框架Quartz详解-基础篇
Quartz任务调度入门
Quartz的基本使用之入门(2.3.0版本)
quartz使用案例篇【面试+工作】
quartz定时器入门(二)-通过配置文件配置quartz工程
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服