Quartz 在Java Web Application中使用
Quartz定时发送消息的功能,该功能依附于Web应用上,即当Web应用启动时,该应用就开始作用。起先决定使用java.util.Timer和java.util.TimerTask来实现,但是研究了一下以后发现Java Timer的功能比较弱,而且其线程的范围不受Web应用的约束。后来发现了Quartz这个开源的调度框架,非常有趣。
首先我们要得到Quartz的最新发布版。目前其最新的版本是1.6。我们可以从以下地址获得它的完整下载包,包中可谓汤料十足,不仅有我们要的quartz.jar,更包含多个例程和详细的文档,从API到配置文件的XSD一应俱全。感兴趣的朋友也可以在src目录下找到该项目的源码一看究竟。
废话少说,下面就来看一看这个东东是怎么在Java Web Application中得以使用的。
首先不得不提出的是Quartz的三个核心概念:调度器、触发器、作业。让我们来看看他们是如何工作的吧。
一.作业总指挥——调度器
1.Scheduler接口
该接口或许是整个Quartz中最最上层的东西了,它提携了所有触发器和作业,使它们协调工作。每个Scheduler都存有JobDetail和Trigger的注册,一个Scheduler中可以注册多个JobDetail和多个Trigger,这些JobDetail和Trigger都可以通过group name和他们自身的name加以区分,以保持这些JobDetail和Trigger的实例在同一个Scheduler内不会冲突。所以,每个Scheduler中的JobDetail的组名是唯一的,本身的名字也是唯一的(就好像是一个JobDetail的ID)。Trigger也是如此。
Scheduler实例由SchedulerFactory产生,一旦Scheduler实例生成后,我们就可以通过生成它的工厂来找到该实例,获取它相关的属性。下面的代码为我们展示了如何从一个Servlet中找到SchedulerFactory并获得相应的Scheduler实例,通过该实例,我们可以获取当前作业中的testmode属性,来判断该作业是否工作于测试模式。
//从当前Servlet上下文中查找StdSchedulerFactory
ServletContext ctx=request.getSession().getServletContext();
StdSchedulerFactory factory = (StdSchedulerFactory) ctx.getAttribute("org.quartz.impl.StdSchedulerFactory.KEY");
Scheduler sch = null;
try {
//获取调度器
sch = factory.getScheduler("SchedulerName");
//通过调度器实例获得JobDetail,注意领会JobDetailName和GroupName的用法
JobDetail jd=sch.getJobDetail("JobDetailName", "GroupName");
Map jobmap1=jd.getJobDataMap();
istest=jobmap1.get("testmode")+"";
} catch (Exception se) {
//如果得不到当前作业,则从配置文件中读取testmode
ReadXML("job.xml").get(“job.testmode”);
}
Scheduler实例生成后,它处于"stand-by"模式,需要调用其start方法来使之投入运作。
public class SendMailShedule{
//设置标准SchedulerFactory
static SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
static Scheduler sched;
public static void run()throws Exception{
//生成Scheduler实例
sched = schedFact.getScheduler();
//创建一个JobDetail实例,对应的Job实现类是SendMailJob
JobDetail jobDetail = new JobDetail("myJob",sched.DEFAULT_GROUP,SendMailJob.class);
//设置CronTrigger,利用Cron表达式设定触发时间
CronTrigger trigger = new CronTrigger("myTrigger","test","0 0 8 1 * ?");
sched.scheduleJob(jobDetail, trigger);
sched.start();
}
public static void stop()throws Exception{
sched.shutdown();
}
}
另外,我们也可以通过监听器来跟踪作业和触发器的工作状态。
二.作业及其相关
1. Job
作业实际上是一个接口,任何一个作业都可以写成一个实现该接口的类,并实现其中的execute()方法,来完成具体的作业任务。
2. JobDetail
JobDetail可以指定我们作业的详细信息,比如可以通过反射机制动态的加载某个作业的实例,可以指定某个作业在单个调度器内的作业组名称和具体的作业名称,可以指定具体的触发器。
一个作业实例可以对应多个触发器(也就是说学校每天10点放一次眼保健操录音,下午3点半可以再放一次),但是一个触发器只能对应一个作业实例(10点钟的时候学校不可能同时播放眼保健操和广播体操的录音)。
3. JobDataMap
这是一个给作业提供数据支持的数据结构,使用方法和java.util.Map一样,非常方便。当一个作业被分配给调度器时,JobDataMap实例就随之生成。
Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。
如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
JobDataMap实例也可以与一个触发器相关联。这种情况下,对于同一作业的不同触发器,我们可以在JobDataMap中添加不同的数据,以便作业在不同时间执行时能够提供更为灵活的数据支持(学校上午放眼保健操录音第一版,下午放第二版)。
不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。
三.触发器
Trigger是一个抽象类,它有三个子类:SimpleTrigger,CronTrigger和NthIncludedDayTrigger。前两个比较常用。
1。SimpleTrigger:这是一个非常简单的类,我们可以定义作业的触发时间,并选择性的设定重复间隔和重复次数。
2。CronTrigger:这个触发器的功能比较强大,而且非常灵活,但是你需要掌握有关Cron表达式的知识。如果你是一个Unix系统爱好者,你很可能已经具备这种知识,但是如果你不了解Cron表达式,请看下面的Cron详解:
Cron表达式由6或7个由空格分隔的时间字段组成,如表1所示:
表1 Cron表达式时间字段
位置
|
时间域名
|
允许值
|
允许的特殊字符
|
1
|
秒
|
0-59
|
, - * /
|
2
|
分钟
|
0-59
|
, - * /
|
3
|
小时
|
0-23
|
, - * /
|
4
|
日期
|
1-31
|
, - * ? / L W C
|
5
|
月份
|
1-12
|
, - * /
|
6
|
星期
|
1-7
|
, - * ? / L C #
|
7
|
年(可选)
|
空值1970-2099
|
, - * /
|
Cron表达式的时间字段除允许设置数值外,还可使用一些特殊的字符,提供列表、范围、通配符等功能,细说如下:
●星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,*在分钟字段时,表示“每分钟”;
●问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;
●减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;
●逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;
●斜杠(/):x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;
●L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五;
●W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围;
●LW组合:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日;
●井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;
● C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。表2下面给出一些完整的Cron表示式的实例:
表2 Cron表示式示例
表示式
|
说明
|
"0 0 12 * * ? "
|
每天12点运行
|
"0 15 10 ? * *"
|
每天10:15运行
|
"0 15 10 * * ?"
|
每天10:15运行
|
"0 15 10 * * ? *"
|
每天10:15运行
|
"0 15 10 * * ? 2008"
|
在2008年的每天10:15运行
|
"0 * 14 * * ?"
|
每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59。
|
"0 0/5 14 * * ?"
|
每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55。
|
"0 0/5 14,18 * * ?"
|
每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。
|
"0 0-5 14 * * ?"
|
每天14:00点到14:05,每分钟运行一次。
|
"0 10,44 14 ? 3 WED"
|
3月每周三的14:10分到14:44,每分钟运行一次。
|
"0 15 10 ? * MON-FRI"
|
每周一,二,三,四,五的10:15分运行。
|
"0 15 10 15 * ?"
|
每月15日10:15分运行。
|
"0 15 10 L * ?"
|
每月最后一天10:15分运行。
|
"0 15 10 ? * 6L"
|
每月最后一个星期五10:15分运行。
|
"0 15 10 ? * 6L 2007-2009"
|
在2007,2008,2009年每个月的最后一个星期五的10:15分运行。
|
"0 15 10 ? * 6#3"
|
每月第三个星期五的10:15分运行。
|
好,说了这么多,最后让我们来看看如何在Web应用中使用Quartz
由于Scheduler的配置相当的个性化,所以,在Web应用中,我们可以通过一个quartz.properties文件来配置QuartzServlet。不过之前让我们先来看看web.xml中如何配置
web.xml
<servlet>
<servlet-name>
QuartzInitializer
</servlet-name>
<display-name>
Quartz Initializer Servlet
</display-name>
<servlet-class>
org.quartz.ee.servlet.QuartzInitializerServlet
</servlet-class>
<load-on-startup>
-1
</load-on-startup>
<init-param>
<param-name>config-file</param-name>
<param-value>/quartz.properties</param-value>
</init-param>
<init-param>
<param-name>shutdown-on-unload</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>start-scheduler-on-load</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
这里,load-on-startup是指定QuartzServlet是否随应用启动,-1表示否,正数表示随应用启动,数值越小,则优先权越高。初始化参数中,config-file里面可以指定QuartzServlet的配置文件,这里我们用的是quartz.properties。shutdown-on-unload,表示是否在卸载应用时同时停止调度,该参数推荐true,否则你的tomcat进程可能停不下来。start-scheduler-on-load,表示应用加载时就启动调度器,如果为false,则quartz.properties中指定的调度器在用户访问这个Servlet之后才会加载,在此之前,如果你通过ServletContext查找SchedulerFactory是可以找到的,但是要得到具体的Scheduler,那么你一定会发现Jvm抛出了一个NullPointerExcetion。
下面就来看看quartz.properties的真面目。
quartz.properties
org.quartz.scheduler.instanceName = PushDBScheduler
org.quartz.scheduler.instanceId = one
orgorg.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 4
org.quartz.threadPool.threadPriority = 4
orgorg.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.fileName = quartz_job.xml
我想不用多说,大家都看出来了,首先配置了基本的Scheduler实例名,并分配了ID,然后为这个调度器设定了线程池,后面是初始化插件。初始化插件是Quartz非常实用的功能,你可以用这个功能来实现Quartz的扩展性。这里配置的插件是读取job XML文件,让调度器自动载入Job。这个插件现在支持读取多个job XML文件,但是我现在还没有试过,感兴趣的读者可以自己尝试。另外就是有一个scanInterval属性,表示每隔几秒自动扫描一次job XML文件,我现在也没有试过,感兴趣的读者可以自己试验一下。注意,该参数设定为0表示不扫描。
最后,我们来看看job XML文件,这里以quartz_job.xml为例
quartz_job.xml
<quartz>
<job>
<job-detail>
<name>ScanItemsInDB</name>
<group>Scanning</group>
<job-class>com.testquartz.ScanDB</job-class>
<job-data-map allows-transient-data="true">
<entry>
<key>testmode</key>
<value>true</value>
</entry>
</job-data-map>
</job-detail>
<trigger>
<cron>
<name>t1</name>
<group> Scanning </group>
<job-name> ScanItemsInDB </job-name>
<job-group> Scanning </job-group>
<cron-expression>0 0/5 * * * ?</cron-expression>
</cron>
</trigger>
</job>
</quartz>
这个文件真是非常显而易见了,我就不多说了,大家自己研究吧。
然后你只要自己写一下ScanDB这个类就可以了。
ScanDB.java
public class ScanDB implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
//你的代码
}
}
注意JobExecutionContext这个类。这个类是用来存取任务执行时的相关信息的,从中我们可以获取当前作业的Trigger、Scheduler、JobDataMap等等。
当然,Scheduler也有对应的SchedulerContext,具体的用途很像ServletContext。有兴趣的读者自己研究吧。
另外就是可以提供一个提示:在一个作业执行的时候,你就可以设定另外一个调度器,去执行另一个Job,这样你可以每个一段时间扫描一下数据库,然后看一看数据库里有没有下一个时间段待发的邮件,然后调用一个新的调度器实例,以便在指定的发送时间将其发送出去。
分享到:
相关推荐
Web应用程序框架这是一个易于使用,支持gradle的纯Java,轻量级和可伸缩性,并且没有繁琐的JEE内容,Web应用程序模板,该模板包含所有主要模块,通常是每个典型项目所需的-后端,带有客户端的REST API,Web用户界面...
14)、JAVA代码混淆使用Allatori。 15)、获取系统信息使用Sigar。 16)、单元测试使用JUnit。 17)、配置文件加密使用Jasypt。 18)、缓存使用EhCache或是Memcached。 19)、数据库连接池使用CommonsDBCP。 20)、...
java安卓辅助源码 ObserverEye ObserverEye(观察者)是一个用于监控 Android & WEB 应用的轻量级开源程序。 Android 部分使用 Appium 实现,任务调度使用 Quartz ,基础框架使用 SpringBoot,持久层使用 JPA & ...
使用IDEA打开该项目,并把application.properties中数据信息修改为自己的数据信息即可。同时把dynamicquartz的sql文件导入到数据中即可。项目地址: 项目如下图所示: Quartz quartz是一个java编写的开源任务调度...
整体设计架构: 之所以采用关系数据库和NoSQL混合模式,是因为系统有很多视频和图片文件,而且需要 保存历史,所以这类数据存放在NoSQL数据库中。 展现层: Spring MVC - MVC Framework Java Server Pages (JSP) - ...
石英+ springboot + RabbitMQ + H2(内存数据库)+动态任务管理Web控制台。 产品特点 提供动态任务管理Web控制台 ... 文件Receiver.java和QuartzJobFactory.java和RabbitConfig.java注释代码 参考资料
seam开发资料:Seam is a fully featured application framework on top of Java EE 5. It is also one of the most popular enterprise Java framework today. Seam deeply integrates many other standard-based ...
3.4.2、在application.properties创建以下配置 9 3.4.3、后台代码 9 3.5、全局捕获异常 10 四、 数据访问 10 4.1、springboot整合使用JdbcTemplate 10 4.2、springboot整合使用mybatis 12 4.3、springboot整合...
6.8.1. 在Spring中使用AspectJ来为domain object进行依赖注入 6.8.1.1. @Configurable object的单元测试 6.8.1.2. 多application context情况下的处理 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来...
6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...
6.8.4. 在Spring应用中使用AspectJ Load-time weaving(LTW) 6.9. 其它资源 7. Spring AOP APIs 7.1. 简介 7.2. Spring中的切入点API 7.2.1. 概念 7.2.2. 切入点实施 7.2.3. AspectJ切入点表达式 7.2.4. ...
6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...
6.8.4. 在Spring应用中使用AspectJ Load-time weaving(LTW) 6.9. 其它资源 7. Spring AOP APIs 7.1. 简介 7.2. Spring中的切入点API 7.2.1. 概念 7.2.2. 切入点实施 7.2.3. AspectJ切入点表达式 7.2.4. ...
Detecting Web Application Type 43.3.2. Detecting Test Configuration 43.3.3. Excluding Test Configuration 43.3.4. Testing with a running server 43.3.5. Using JMX 43.3.6. Mocking and Spying Beans 43.3....
Packages ...com.kingdee.bos.metadata.code.webui ...com.kingdee.bos.util.backport.concurrent.helpers Auxiliary and helper classes for backport.util.concurrent, NOT present in java.util.concurrent. ...
Learn how to add scheduling to your Spring application with Quartz. Learn how to simplify mail sending with Spring and how to integrate JMS messaging into your application using Spring and ActiveMQ. ...
* 在线新增数据库并直接生成 前,后台基本源码,放到源码相应目录中重启tomcat可直接使用,预览 * 系统定时任务的新增改查 立即启动 暂停 恢复 ## 技术框架 * 核心框架:`SpringBoot` * 安全框架:`...
</p><p>BudWk(原名 NutzWk) 是有多年历史的Java Web开源开发框架,其6.x 是Java 微服务分布式 + 前后端完全分离版本,采用nutzboot(nutz核心)、dubbo、redis、zookeeper、shiro、quartz、beetl、logback、sentinel...
并在len-web/pom.xml、父 pom.xml 删除依赖以及模块,删除application.java 中对activiti的扫描即可完全删除工作流模块。 技术: jdk:1.8 核心框架:spring boot 2.1.9.RELEASE 安全框架:Apache Shiro 工作流引擎...
Java EE 框架..................................................................................................................................................................................1 ...