基于Spring的任务调度(3)

2 使用OpenSymphony Quartz来调度任务

      开源Quartz项目是一个专用的任务调度引擎,它可以在Java EE和Java SE中使用。Quartz提供了极其全面的特性,例如持久化任务、集群和分布式事务。在本文中不会涉及集群或分布式事务的特性--你可以在www.opensymphony.com/quartz发现更多的信息。Spring对Quartz的集成与Spring对Timer的集成在任务的声明式配置、触发器和调度上都非常相似。除此之外,Spring还提供额外的任务持久化特性,可以让Quartz的调度加入到Spring事务管理机制中。

2.1 Quartz简介

      Quartz是一个极其强大的任务调度引擎,我们不能期望在本文的剩余部分中覆盖到Quartz的所有方面。但是我们将讨论Quartz与Spring相关的主要方面,同时我们也将介绍如何在Spring应用程序中使用Quartz。和我们对Timer的介绍一样,我们先对Quartz有一个初步了解,然后再讨论Quartz与Spring的集成。 Quartz的核心由两个接口和两个类组成:Job和Scheduler接口,JobDetail和Trigger类。从它们的名字可以了解它们的主要用途。只有JobDetail类的角色不太清晰。不同于基于Timer的任务调度,任务并不是从一个实现Job接口的类的实例开始的。实际上,Quartz将在它需要时再创建Job类的实例。你可以使用JobDetail类封装任务状态,并传递信息给一个任务或在任务的连续执行中间保存信息。使用基于Timer的任务调度,并没有关于自己封装触发器逻辑的触发器概念。Quartz支持一种可插拔触发器的架构,这可以让你在合适的时机创建你自己的实现。但是,你很少会需要创建你自己的Trigger实现,因为Quartz已经提供了极其强大的CronTrigger类,可以让你使用Cron表达式来对任务执行进行细粒度的控制。

1.) 简单的任务调度

      用Quartz创建一个任务你只需简单创建一个实现Job接口的类。Job接口定义了一个execute()方法,你可以用它调用你的业务逻辑。Quartz传递JobExecutionContext实例给execute()方法,这可以让你访问当前执行任务的上下文数据。我们将在"使用JobDataMap类"小节详细介绍它。例14展示了一个简单的Job实现,它只向标准输出发送"Hello World"消息。 例14 创建一个简单任务

package cn.hurraysoft.quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloWorldJob implements Job {

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		System.out.println("Hello World!");
	}

}

  为了调度此任务执行,我们需要先得到一个Scheduler实例,然后创建一个包含任务信息的JobDetail bean,最后创建一个Trigger管理任务的执行。例15所示。 例15 在Quartz中调度任务

package cn.hurraysoft.quartz;

import java.util.Date;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdScheduler;
import org.quartz.impl.StdSchedulerFactory;

public class HelloWorldSchedule {
	public static void main(String[] args) throws SchedulerException {
		Scheduler scheduler=new StdSchedulerFactory().getScheduler();
		scheduler.start();
		JobDetail jobDetail=new JobDetail("HelloWorldJob",Scheduler.DEFAULT_GROUP,HelloWorldJob.class);
		Trigger trigger=new SimpleTrigger("simpleTrigger",Scheduler.DEFAULT_GROUP,new Date(),null,SimpleTrigger.REPEAT_INDEFINITELY,3000);
		scheduler.scheduleJob(jobDetail, trigger);
	}
}

      代码的开始使用StdSchedulerFactory来得到一个Scheduler实例。我们不准备深入讨论这个类的细节,但你可以从OpenSymphony Web网站上的Quartz文档中查阅更多信息。现在,我们知道StdSchedulerFactory.getScheduler()返回一个可运行的Scheduler实例。在Quartz中,一个Scheduler可以被启动(start)、中止(stop)和暂停(pause)。如果Scheduler没有启动或暂停,则没有触发器被激活,那么我们可以使用start()方法启动Scheduler。 下一步,我们创建调度任务的JobDetail实例,并传递3个参数给构造方法。第一个参数是任务名,它用于引用此任务。当你使用一个Scheduler接口的管理方法时(如pauseJob()),任务将暂停。第二个参数是任务组名,这里使用默认名,任务组名用于引用集合起来的一组任务,如你可以使用Scheduler.pauseJobGroup()方法暂停一组任务。你应该注意每个组中的任务名是唯一的。第三个即最后一个参数,是实现了特定任务的类。 创建JobDetail实例后,我们继续创建一个Tirgger。在本例中,我们使用SimpleTrigger类,它提供JDK Timer风格的触发器行为。传递给SimpleTrigger构造方法的第一个和第二个参数分别是触发器名和任务组名。这两个参数和JobDetail的参数作用相似。触发器名在它所在的任务组中必须是唯一的,否则会抛出一个异常。第三个和第四个参数都是Date类型,标识触发器的启动和结束时间。通过把结束时间设置为null,我们表明任务没有结束时间。这个为触发器指定结束时间的能力,在使用Timer时是不可用的。下一个参数是重复计数,这可以让你指定Tirgger被激发的最大次数。我们可以使用REPEAT_INDEFINITELY来让触发器可以被激发无限次。最后一个参数是Trigger激发的时间间隔,是一个毫秒数。我们已经定义了一个3s的时间间隔。 示例的最后步骤是调用Scheduler.schedule()来调度任务,使用JobDetail实例和Trigger实例作为参数。如果你运行这个应用程序,你会看到熟悉的"Hello World!"信息流不断地被发送到控制台。

2.) 使用JobDataMap类

      在先前的示例中,关于任务执行的所有信息都是从任务本身得到的。但你也可以通过JobDetail类或Trigger类将状态传递给任务。每个JobDetail实例都有一个相关的JobDataMap实例,它实现了Map接口并可以让你使用键值对将与任务相关的数据传递给任务。但使用这种方法时,有一些与任务相关的因素需要斟酌一下。我们将在后面名为"关于任务持久化"小节讨论。 当你使用多个Trigger实现调度同一个任务,或在每次独立触发上为任务提供不同的数据时,Trigger的存储数据是有用的。map的入口可以通过JobExecutionContext上的JobDataMap访问,而JobExecutionContext可以使用getMergedJobDataMap()方法获得。正如方法名所暗示的,JobExecution- Context上的JobDataMap是JobDetail和Trigger上的JobDataMap的合并,而若数据有冲突,存储在Trigger中的数据将覆盖存储在JobDetail中的数据。例16中,你可以看到一个Job的示例,它使用合并后的JobDataMap数据执行任务处理。 例16 使用JobDataMap类

package cn.hurraysoft.quartz;

import java.util.Map;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class MessageJob implements Job {

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		Map properties=context.getMergedJobDataMap();
		System.out.println("Previous Fire Time"+context.getPreviousFireTime());
		System.out.println("Current Fire Time"+context.getFireTime());
		System.out.println("Next Fire Time"+context.getNextFireTime());
		System.out.println(properties.get("message"));
		System.out.println(properties.get("jobDetailMessage"));
		System.out.println(properties.get("triggerMessage"));
	}
}

 

      我们可以从合并后的JobDataMap中抽取对象,它使用message、jobDetailMessage和triggerMessage作为关键词并将其发送到标准输出。也要注意我们可以从JobExecutionContext得到该任务上一次、当前和下一次执行的相关信息。例17中,你可以看到一个关于调度任务时如何用JobDetail的数据填充JobDataMap的示例。

例17 向JobDetail的JobDataMap中添加数据

package cn.hurraysoft.quartz;

import java.util.Date;
import java.util.Map;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdScheduler;
import org.quartz.impl.StdSchedulerFactory;

public class HelloWorldSchedule {
	public static void main(String[] args) throws SchedulerException {
		Scheduler scheduler=new StdSchedulerFactory().getScheduler();
		scheduler.start();
		JobDetail jobDetail=new JobDetail("MessageJob",Scheduler.DEFAULT_GROUP,MessageJob.class);
		Map jobDetailMap=jobDetail.getJobDataMap();
		jobDetailMap.put("message", "This is a message from Quartz");
		jobDetailMap.put("jobDetailMessage", "A jobDetail message");
		Trigger trigger=new SimpleTrigger("simpleTrigger",Scheduler.DEFAULT_GROUP,new Date(),null,SimpleTrigger.REPEAT_INDEFINITELY,3000);
		scheduler.scheduleJob(jobDetail, trigger);
	}
}

你可能发现这里的很多代码都和例15中的一样。但需注意的是,一旦JobDetail实例被创建,我们就会访问JobDataMap并向它增加两条消息,关键词分别为message和jobDetailMessage。若运行这个例子,并让它反复运行几次任务,你会得到和下面相似的输出:

 

Previous Fire Time: null
Current Fire Time: Wed Mar 17 22:30:38 CST 2010
Next Fire Time: Wed Mar 17 22:30:41 CST 2010
This is a message from Quartz
A jobDetail message
null
Previous Fire Time: Wed Mar 17 22:30:38 CST 2010
Current Fire Time: Wed Mar 17 22:30:41 CST 2010
Next Fire Time: Wed Mar 17 22:30:44 CST 2010
This is a message from Quartz
A jobDetail message
null
Previous Fire Time: Wed Mar 17 22:30:41 CST 2010
Current Fire Time: Wed Mar 17 22:30:44 CST 2010
Next Fire Time: Wed Mar 17 22:30:47 CST 2010
This is a message from Quartz
A jobDetail message
null

 

      你可以看到在前一次、当前和下一次执行时间的信息显示后,保存在JobDataMap中的两条消息被发送到标准输出。例子18展示了一个示例,它也是在Trigger上提供数据和合并两个JobDataMap实例数据值。

例18 在Trigger上使用JobDataMap

 

package cn.hurraysoft.quartz;

import java.util.Date;
import java.util.Map;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdScheduler;
import org.quartz.impl.StdSchedulerFactory;

public class HelloWorldSchedule {
	public static void main(String[] args) throws SchedulerException {
		Scheduler scheduler=new StdSchedulerFactory().getScheduler();
		scheduler.start();
		JobDetail jobDetail=new JobDetail("MessageJob",Scheduler.DEFAULT_GROUP,MessageJob.class);
		Map jobDetailMap=jobDetail.getJobDataMap();
		jobDetailMap.put("message", "This is a message from Quartz");
		jobDetailMap.put("jobDetailMessage", "A jobDetail message");
		Trigger trigger=new SimpleTrigger("simpleTrigger",Scheduler.DEFAULT_GROUP,new Date(),null,SimpleTrigger.REPEAT_INDEFINITELY,3000);
		trigger.getJobDataMap().put("message","Message Form Trigger");
		trigger.getJobDataMap().put("triggerMessage","Anther Trigger Message");
		scheduler.scheduleJob(jobDetail, trigger);
	}
}
 

      正如所见到的,JobDetail使用和先前相同的配置。我们只是向Trigger增加两条消息:作为关键字的消息和触发器消息。运行这个实例产生与下面内容相似的输出:

 

Previous Fire Time: null
Current Fire Time: Wed Mar 17 22:40:46 CST 2010
Next Fire Time: Wed Mar 17 22:40:49 CST 2010
Message Form Trigger
A jobDetail message
Anther Trigger Message
Previous Fire Time: Wed Mar 17 22:40:46 CST 2010
Current Fire Time: Wed Mar 17 22:40:49 CST 2010
Next Fire Time: Wed Mar 17 22:40:52 CST 2010
Message Form Trigger
A jobDetail message
Anther Trigger Message
Previous Fire Time: Wed Mar 17 22:40:49 CST 2010
Current Fire Time: Wed Mar 17 22:40:52 CST 2010
Next Fire Time: Wed Mar 17 22:40:55 CST 2010
Message Form Trigger
A jobDetail message
Anther Trigger Message
 

 

      注意关键词message的值是来自Trigger JobDataMap,而不是我们在JobDetail中定义的。
可以看到,使用Spring配置Quartz任务调度时,可以在Spring配置文件中创建JobDataMap,这可以让你将所有的任务配置在程序外部完成。

3)使用CronTrigger类

      在先前示例中,我们使用SimpleTrigger类,它提供的触发器功能和JDK Timer类提供的功能非常相似。然而,Quartz的优势是在于它使用CronTrigger提供对复杂触发器表达式的支持。CronTrigger是基于Unix Cron守护进程,它是一个调度程序,支持简单而强大的触发器语法。你可以使用CronTrigger快速并精确地定义SimpleTrigger类不可能或很难处理的触发器表达式。例如,你可以定义一个触发器,它"在14:00到17:00之间,从每分钟的第3 s开始,每隔5 s触发一次"或"在每个月的最后一个星期五触发"。 CronTrigger语法表达式,也叫Cron表达式,包括6个必需组件和一个可选组件。Cron表达式单独写一行,并且每个组件间用空格隔开。只有最后或者说最右边的组件是可选的。表1详细说明了Cron的各个组件。

表1 Cron表达式的组件

特殊字符

说明

*

任意值。这个特殊字符可以被使用在表达式的任何域,

表示不应该检查该值。因此,我们的Cron表达式可以在

19702099年的任意一天,任意月份和星期中的任意一天触发

?

无特定值。此特殊字符通常和其他指定的值一起使用,

表示必须显示该值但不能检查

-

范围。例如小时部分10-12表示10:0011:0012:00

,

列分隔符。此特殊字符可以让你指定一系列的值,

例如在星期域中指定MONTUEWED

/

增量。此特殊字符表示一个值的增量,例如0/1表示从0开始,

每次增加1 min

L

L是英文单词Last的缩写。它在日期和星期域中表示有一点不同,

挡在日期域中使用时,表示这个月的最后一天(331

229日等)。当使用在星期域时,它永远是同一个值:7——

星期六。当你希望使用星期中某一天时,L字符非常有用。例如,

星期域中6L表示每个月的最后一个星期五

W

W只可以用在月域部分的天字段,指定临近一周(星期一到星期五)

给该月的既定日期。设置值7W,触发器若第7此触发在一个星期六,

那么它将在第六天触发。若第七天是星期日,触发器将在星期一触发,

即第八天。注意触发器由于周六是第一天,实际触发发生在第三天

#

此特殊字符可使用在星期域中,表示该月的第几个星期。

例如1#2表示每个月的第一个星期一

C

日历值。它可以使用在日期和星期域中。日期值是根据一个

给定的日历计算出来的。在日期域中给定一个20C将在20

(日历包括20日)或20日后日历中包含的第一天(不包括20日)

激活触发器。例如在一个星期域中使用6C表示日历中星期五

(日历包括星期五)或者第一天(日历不包括星期五)


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值