Quartz使用

一:简单使用

Quartz是一个开源的任务调度服务,它可以独立使用,也可与其它的Java EE,Java SE应用整合使用。无论是执行十个,一百个工作的简单任务,还是执行成百上千个工作的复杂调度任务都可以使用Quartz来实现,此外,Quartz还提供了很多企业级应用的特色,如JTA事务支持及集群。

如果你的应用中有任务需要在特定的时间执行,亦或周期循环执行,则Quartz也许是你理想的选择。

Quartz的使用

作为一个Javaer,自然知道第一步是下载Quartz的jar包,Quartz API中一些关键的接口有:

  • Scheduler – 这是与调度器交互的主要API,通过它来调度任务的执行。
  • Job  – 你的任务需要实现的接口,执行调度任务时,Scheduler会执行此接口的execute方法;
  • JobDetail – 定义Jobs的实例,里面包含了Job的所有信息:名称、所属的组,Job的类等;
  •  Trigger – 任务的触发器,由它来定义计划,规定Job何时被触发执行。

因此,可以这么理解,在我们应用Quartz时,完成具体工作的组件(ExportJob)需要实现Job接口,并在execute方法中调用具体的业务逻辑代码。而对于Quartz来说,每个Job的实例由JobDetail来描述,里面包括Job的实现类(ExportJob.class)等属性,Trigger则与调度器关联,决定何时触发Job的执行。最后,由Scheduler来管理所有的Jobs与Trigger。

Scheduler的生命周期从它被创建开始,一直到调用其shutdown()方法结束。一旦创建以后,便可通过Scheduler接口执行添加、删除、显示Jobs和Triggers,此外,还可执行其它相关的调度操作,如暂停Trigger。但是,所有Triggers相关的操作都需要在执行start()方法以后才能进行。

一个简单的实例如:

1. 执行业务逻辑的Job

public class SimpleJob implements Job{

	public void execute(JobExecutionContext paramJobExecutionContext)
			throws JobExecutionException {
		System.out.println("在后台解析日志信息");
		System.out.println("开始!");
		//……
		System.out.println("结束!");
	}

}

2. 任务调度

public class QuartzSimpleExample {

	public static void main(String args[]) {

		try {
			// 创建Scheduler
			Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
			scheduler.start();

			// 创建Job的实例
			JobDetail jobIns = JobBuilder.newJob(SimpleJob.class).withIdentity("simpleJob",
					"group1").build();

			// 创建Trigger
			SimpleScheduleBuilder buider = SimpleScheduleBuilder
					.simpleSchedule().withIntervalInMinutes(1).repeatForever();

			Trigger trigger = TriggerBuilder.newTrigger().withIdentity(
					"simpleTrigger", "group1").startNow().withSchedule(
					buider).build();

			//调度执行
			scheduler.scheduleJob(jobIns, trigger);

			try {
				Thread.sleep(60000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			scheduler.shutdown();

		} catch (SchedulerException e) {
			e.printStackTrace();
		}

	}
}

如果你也看了Quartz的参考文档,可能会发现有些不一样,文档上面是简化的,这里加了一些东西,才能正常运行,特别是文档上坑新人,通过静态import,直接就newJob****。




二:深入理解Trigger和cron


Quartz提供了几个Trigger类型供用户使用,对应的实现可以在org.quartz.impl.triggers中看到,一般情况下,用户用得最多的是SimpleTrigger与CronTrigger。对于最简单的情况,如只需Job在某一个特定的时间执行一次,亦或在某个时间之后按一定的时间间隔重复执行,则可使用简单的SimpleTrigger,至于CronTrigger,如果你了解cron表达式(对于linux牛,应该知道,因为linux的定时任务cron也是如此这般),便知道它几乎可以适应所有复杂的调度情况。

对于SimpleTrigger,可通过SimpleScheduleBuilder来创建,在前面的示例中已经用到,不过SimpleScheduleBuilder里还有很多方法没有使用到,可以自己看看,了解自己可以设置哪些属性。

CronTrigger的魔力在于cron表达式,cron表达式是一个由7个子表达式组成的字符串,各个子表达式通过空白符分隔,分别描述了调度的详细信息。

  • 小时
  • 每月的第几天
  • 每周的第几天
  • 年(可选的)

如”0 0 12 ? * WED”,表示每个礼拜三的12点。

子表达式中可以包含范围或列表,还可以同时包含两者,如上面例子中,WED可以是MON-FRI, MON,WED,FRI, 或MON-WED,SAT。

通配符”*”表示这个域的所有可能取值,如上面月部分用了*表示每个月,同理,如果在“每周的第几天”用*则表示每个礼拜的任意一天。

字符“/”可以用来指定增长值,如你在”分钟”中指定”0/15”则表示从零开始,每隔15分钟,”3/20”则是从第三分钟开始,每隔20分钟,这跟直接指定”3,23,43”意思是一样的。

字符”?”可以在“每月的第几天”和“每周的第几天”中使用,表示不指定任何的值。当你需要指定这两个子表达式中一个的值,而不指定另一个的值时,它便派上用处了。(其实想想,如果同时指定两个值貌似有点那个=。=)

字符”L”同样只可以在“每月第几天”和“每周第几天”中使用,L是”last”的缩写,因为表示的是最后,所以它在这两个子表达式中的含义也是变化的,如果是一月则表示31号,2月则表示28号,如何与“每周第几天”搭配,则表示礼拜天,等等。

字符”#”用于表示每个月的第几个礼拜几,如”6#3”或”FRI#3”表示每个月的第三个礼拜五。

下面是一些相关的例子:

“0 0/5 * * * ?” : 每隔5分钟触发一次

“10 0/5 * * * ?” : 从每分钟的第10秒开始,每隔五分钟触发

“0 30 10-13 ? * WED,FRI” : 每个周三,周五的10:30,11:30,12:30和13:30触发

 

了解了cron表达式,接下来看看如何创建CronTrigger?

创建SimpleTrigger时使用了SimpleScheduleBuilder,自然创建CronTrigger将使用CronScheduleBuilder,你可以自己编写cron表达式,然后通过cronSchedule()函数来创建,如果CronScheduleBuilder提供的一些调度函数(如dailyAtHourAndMinute)正好能满足你的需求,也可是直接使用,免去编写cron表达式的需要。

示例代码:

public class QuartzCronExample {

	public static void main(String[] args) {
		try {
			// 创建Scheduler
			Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
			scheduler.start();

			// 创建Job的实例
			JobDetail jobIns = JobBuilder.newJob(SimpleJob.class).withIdentity("simpleJob",
					"group1").build();

			// 创建Trigger
			CronScheduleBuilder builder = CronScheduleBuilder.cronSchedule("0 0/1 * * * ?");

			/**
			builder = CronScheduleBuilder.dailyAtHourAndMinute(12, 30);
			**/

			Trigger trigger = TriggerBuilder.newTrigger().withIdentity(
					"simpleTrigger", "group1").startNow().withSchedule(
					builder).build();

			//调度执行
			scheduler.scheduleJob(jobIns, trigger);

			try {
				Thread.sleep(600000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			scheduler.shutdown();

		} catch (SchedulerException e) {
			e.printStackTrace();
		}
	}

}

有点坑,跟上一篇的示例是一样的,除了CronScheduleBuilder这个点。


三:scheduler及事件机制

(1)     Scheduler

在这之前,我们创建Scheduler都是通过下面的语句:

Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

这使用的是默认的调度器,它会依据quartz.properties配置文件为我们设置好相关的属性,包括使用的调度器、线程池、线程池中线程数、采用的JobStore等等。如果我们在自己应用的classpath中写了quartz.properties,则SchedulerFactory会使用它的配置,否则它会使用Quartz的jar包中的配置文件。

Jar包中提供的quartz.properties如:

# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#

org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000

org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

通过查看前面部分的示例程序,查看其输出的日志信息,也可说明这一点:

288 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
293 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
345 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
347 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.1.7 created.
358 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
359 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.1.7) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

359 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
360 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.1.7
362 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.

如果要使用特殊的配置文件(如命名不同等),可通过StdSchedulerFactory的initialize()函数进行初始化,如:

StdSchedulerFactory factory = new StdSchedulerFactory();
factory.initialize("jmatrix_quartz.properties");
Scheduler scheduler = factory.getScheduler();

 

(2)     事件机制

Quartz为Job,Trigger和Scheduler都提供了对应的事件接口,它们分别为:

public abstract interface JobListener
{
  public abstract String getName();

  public abstract void jobToBeExecuted(JobExecutionContext paramJobExecutionContext);

  public abstract void jobExecutionVetoed(JobExecutionContext paramJobExecutionContext);

  public abstract void jobWasExecuted(JobExecutionContext paramJobExecutionContext, JobExecutionException paramJobExecutionException);
}

可监听的事件有,Job开始执行、执行完成、停止。

public abstract interface TriggerListener
{
  public abstract String getName();

  public abstract void triggerFired(Trigger paramTrigger, JobExecutionContext paramJobExecutionContext);

  public abstract boolean vetoJobExecution(Trigger paramTrigger, JobExecutionContext paramJobExecutionContext);

  public abstract void triggerMisfired(Trigger paramTrigger);

  public abstract void triggerComplete(Trigger paramTrigger, JobExecutionContext paramJobExecutionContext, Trigger.CompletedExecutionInstruction paramCompletedExecutionInstruction);
}

可监听的事件有:trigger触发,trigger错过执行,trigger执行完成和停止。

public abstract interface SchedulerListener
{
  public abstract void jobScheduled(Trigger paramTrigger);

  public abstract void jobUnscheduled(TriggerKey paramTriggerKey);

  public abstract void triggerFinalized(Trigger paramTrigger);

  public abstract void triggerPaused(TriggerKey paramTriggerKey);

  public abstract void triggersPaused(String paramString);

  public abstract void triggerResumed(TriggerKey paramTriggerKey);

  public abstract void triggersResumed(String paramString);

  public abstract void jobAdded(JobDetail paramJobDetail);

  public abstract void jobDeleted(JobKey paramJobKey);

  public abstract void jobPaused(JobKey paramJobKey);

  public abstract void jobsPaused(String paramString);

  public abstract void jobResumed(JobKey paramJobKey);

  public abstract void jobsResumed(String paramString);

  public abstract void schedulerError(String paramString, SchedulerException paramSchedulerException);

  public abstract void schedulerInStandbyMode();

  public abstract void schedulerStarted();

  public abstract void schedulerShutdown();

  public abstract void schedulerShuttingdown();

  public abstract void schedulingDataCleared();
}

Scheduler可监听的事件就多了:Job/Trigger的添加与移除、Scheduler错误的发生、Scheduler的关闭等等。

想要创建一个自己的监听器,只需创建一个类并让它实现对应的Listener接口即可,不过由于直接实现接口需要实现所有的接口方法,所以更推荐让自己的监听器类继承各个Listener接口对应的Support类:JobListenerSupport,TriggerListenerSupport和SchedulerListenerSupport,这些Support类提供所有接口方法的默认实现(即不做任何事情),因此我们创建的监听器只需实现自己感兴趣的事件相关方法即可。

一个简单的listener示例:

public class SimpleJobListener extends JobListenerSupport {

	public String getName() {
		return SimpleJobListener.class.getName();
	}

	public void jobToBeExecuted(JobExecutionContext context) {
		System.out.println("开始执行SimpleJob");
	}

	public void jobWasExecuted(JobExecutionContext context,
			JobExecutionException jobException) {
		System.out.println("SimpleJob执行完成");
	}
}

创建好自己的listener后,我们需要将它添加到Schedule中,这个可以通过ListenerManager的addJobListener()函数进行:

// 创建Scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

scheduler.start();

//添加Listener
SimpleJobListener listener = new SimpleJobListener();
scheduler.getListenerManager().addJobListener(listener, KeyMatcher.keyEquals(JobKey.jobKey("simpleJob","group1")));

需要注意的是,这里addJobListener()函数的第二个参数是Matcher接口,通过它来决定此Listener是与那个Job,那个group,亦或所有的Job关联,上面的例子是直接关联到simpleJob。关于Quartz提供的一系列Matcher实现,可以查看org.quartz.impl.matchers包。


四:深入Job和JobDetail

在前面部分,我们知道Job中定义了实际的业务逻辑,而JobDetail包含Job相关的配置信息。在Quartz中,每次Scheduler执行Job时,在调用其execute()方法之前,它需要先根据JobDetail提供的Job类型创建一个Job class的实例,在任务执行完以后,Job class的实例会被丢弃,Jvm的垃圾回收器会将它们回收。

因此编写Job的具体实现时,需要注意:(1) 它必须具有一个无参数的构造函数;(2) 它不应该有静态数据类型,因为每次Job执行完以后便被回收,因此在多次执行时静态数据没法被维护。

Keep moving,在JobDetail中有这么一个成员JobDataMap,JobDataMap是Java Map接口的具体实现,并添加了一些便利的方法用于存储与读取原生类型数据,里面包含了当Job实例运行时,你希望提供给它的所有数据对象。

可以借助JobDataMap为Job实例提供属性/配置,可以通过它来追踪Job的执行状态等等。对于第一种情况,可以在创建Job时,添加JobDataMap数据,在Job的execute()中获取数据,第二种,则可以在Listener中通过获取JobDataMap中存储的状态数据追踪Job的执行状态。

按例,一个简单的例子:

// 创建Job的实例
JobDetail jobIns = JobBuilder.newJob(SimpleJob.class).withIdentity(
		"simpleJob", "group1").usingJobData("domain",
		"www.jmatrix.org").usingJobData("rank", "求别提~~~").build();

Job实现:

public void execute(JobExecutionContext context)
			throws JobExecutionException {
	System.out.println("开始!");

	//……JobDataMap
	JobDataMap dataMap = context.getJobDetail().getJobDataMap();
	System.out.println("域名 : "+dataMap.getString("domain"));
	System.out.println("排名 : "+dataMap.getString("rank"));

	System.out.println("结束!");
}

完成了这些工作,还需决定如何存储Job的数据,Quartz提供了JobStore接口来做这件事,如果你决定将Job数据保存在内存中,则可以使用RAMJobStore,它的优点是速度快,缺点是一旦机器挂了,Job相关的数据也丢失了,如果要采用数据库来存储Job数据,可以使用JobStoreTX或JobStoreCMT,这取决于你采用的事务管理方式,使用RAMJobStore的话配置很简单,只需配置org.quartz.jobStore.class即可,如果使用数据库存储,则还需要配置driverDelegate,tablePrefix及dataSource,driverDelegate一般情况下使用StdJDBCDelegate(MySQL便可使用这个),特殊的可以使用Quartz提供的相关delegate,请查看jar包,一般命名就说明了一切。TablePrefix是你的数据库表前缀,创建数据库的sql文件可以在docs\dbTables目录下找到。最后的数据源dataSource就有点麻烦,Quartz为用户提供了三种创建dataSource的方式:

  1. 配置相关的数据库属性(driverClass,url,username,password等),让Quartz为你创建dataSource。
  2. 通过jndi使用你应用服务器管理的dataSource。
  3. 通过实现org.quartz.utils.ConnectionProvider定制自己的datasource。

前面两种都是依据datasource的名称为其配置相关的属性,具体有哪些属性可直接参考Quartz的文档。

下面说说最后一种,我们需要配置:

org.quartz.dataSource.dbcpDS.connectionProvider.class=cn.ds.vertical.core.service.mail.DbcpConnectionProvider

这样Quartz才能找到自定义的ConnectionProvider实现,DbcpConnectionProvider是我模仿Quartz提供的默认ConnectionProvider实现实现的,只是提供一种思路,Quartz使用的c3p0,而DbcpConnectionProvider使用DBCP作为DataSource实现。

public class DbcpConnectionProvider implements ConnectionProvider {

	private BasicDataSource basicDataSource;

	public DbcpConnectionProvider(){
		initialize();
	}

	public Connection getConnection() throws SQLException {
		return this.basicDataSource.getConnection();
	}

	public void shutdown() throws SQLException {
		this.basicDataSource.close();
	}

	private void initialize(){
		String driverClassName = "com.mysql.jdbc.Driver";
		String url = "jdbc:mysql://localhost:3306/quartz";
		String username = "root";
		String password = "123456";

		this.basicDataSource = new BasicDataSource();
		this.basicDataSource.setDriverClassName(driverClassName);
		this.basicDataSource.setUrl(url);
		this.basicDataSource.setUsername(username);
		this.basicDataSource.setPassword(password);

		this.basicDataSource.setTestWhileIdle(true);
	}
}

 


深入trigger及cron表达式


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值