一、简介

Quartz是一个功能丰富的开源作业调度库,可以用于创建简单或复杂的计划来执行数十数百甚至数万个作业。

二、安装与启动

1、安装

下载并解压,压缩文件中包括文档、样例和Jar等,需要将lib下的jar添加到项目中。

或者使用Maven依赖:

<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>

如果需要使用日志,还要引入:

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.7.25</version>
</dependency>

2、启动

启动并运行一个调度程序:

public class QuartzTest {

	public static void main(String[] args) {

        try {
            // Grab the Scheduler instance from the Factory
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // and start it off
            scheduler.start();

			// do something

            scheduler.shutdown();

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

三、配置

Quartz的配置文件使用一个名为quartz.properties的属性文件,此文件可以不设置,默认使用quartz.jar中的org/quartz/quartz.properties配置文件。

例如,修改默认配置:

# 将调度(计划)程序的名字改为MyScheduler
org.quartz.scheduler.instanceName = MyScheduler
# 设置线程中的线程数为3
org.quartz.threadPool.threadCount = 3
# Quartz的数据(作业和触发器的详细信息等)保存到内存中(而不是数据库中)
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

四、样例

1、HelloWorld

  • Job

Job类需实现org.quartz.Job接口,重写execute()方法:

public class HelloJob implements Job{

	private static Logger log = LoggerFactory.getLogger(HelloJob.class);
	
	/**
     * Empty constructor for job initilization
     * Quartz requires a public empty constructor so that the
     * scheduler can instantiate the class whenever it needs.
     */
	public HelloJob() {
	}
	
	/**
     * Called by the org.quartz.Scheduler when a org.quartz.Trigger fires that is associated with the Job
     * 
     * @throws JobExecutionException
     *             if there is an exception while executing the job.
     */
	public void execute(JobExecutionContext context) throws JobExecutionException {
		log.info("Hello World! - " + new Date());
	}

}
  • Application
public class SimpleExample {

	public static void main(String[] args) throws Exception {
		Logger log = LoggerFactory.getLogger(SimpleExample.class);

		log.info("------- Initializing ----------------------");

		// First we must get a reference to a scheduler
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler sched = sf.getScheduler();

		log.info("------- Initialization Complete -----------");

		// computer a time that is on the next round minute
		Date runTime = new Date();

		log.info("------- Scheduling Job  -------------------");

		// define the job and tie it to our HelloJob class
		JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "group1").build();

		// Trigger the job to run on the next round minute
		Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();

		// Tell quartz to schedule the job using our trigger
		sched.scheduleJob(job, trigger);
		log.info(job.getKey() + " will run at: " + runTime);

		// Start up the scheduler (nothing can actually run until the scheduler has been started)
		sched.start();

		log.info("------- Started Scheduler -----------------");

		// wait long enough so that the scheduler as an opportunity to run the job!
		log.info("------- Waiting 3 seconds... -------------");
		try {
			// wait 3 seconds to show job
			Thread.sleep(3000);
			// executing...
		} catch (Exception e) {
			//
		}

		// shut down the scheduler
		log.info("------- Shutting Down ---------------------");
		sched.shutdown(true);
		log.info("------- Shutdown Complete -----------------");
	}
}
  • log4j.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

  <appender name="default" class="org.apache.log4j.ConsoleAppender">
    <param name="target" value="System.out"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="[%p] %d{dd MMM hh:mm:ss.SSS aa} %t [%c]%n%m%n%n"/>
    </layout>
  </appender>

 <logger name="org.quartz">
   <level value="info" />
 </logger>

  <root>
    <level value="info" />
    <appender-ref ref="default" />
  </root>
  
</log4j:configuration>

2、简单触发器

本示例(SimpleTriggerExample)演示使用简单触发器的Quartz调度功能。

  • SimpleJob.java
public class SimpleJob implements Job{

	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobKey jobKey = context.getJobDetail().getKey();
		System.out.printf("SimpleJob says: %s executing at %s. \n", jobKey, new Date());
	}

}
  • SimpleTriggerExample

Job1只执行一次,Job2每3秒执行一次,重复两次:

public class SimpleTriggerExample {

	public static void main(String[] args) throws Exception {
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();

		Date startTime = new Date();
		//job1
		JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("job1", "group1").build();
		SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
		Date ft = scheduler.scheduleJob(job, trigger);
		System.out.printf("%s will run at: %s and repeat: %s times, every %s seconds.\n", job.getKey(), ft, trigger.getRepeatCount(), trigger.getRepeatInterval() / 1000);
		
		//job2
		job = JobBuilder.newJob(SimpleJob.class).withIdentity("job2", "group1").build();
		trigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group1").startAt(startTime)
				.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(2))
				.build();
		ft = scheduler.scheduleJob(job, trigger);
		
		System.out.printf("%s will run at: %s and repeat: %s times, every %s seconds.\n", job.getKey(), ft, trigger.getRepeatCount(), trigger.getRepeatInterval() / 1000);
		
		scheduler.start();
		try {
	      // wait 10 seconds to show jobs
	      Thread.sleep(10000L);
	      // executing...
	    } catch (Exception e) {
	      //
	    }
		scheduler.shutdown(true);
	}
}
  • 输出
group1.job1 will run at: Sat Feb 13 00:09:08 CST 2021 and repeat: 0 times, every 0 seconds.
group1.job2 will run at: Sat Feb 13 00:09:08 CST 2021 and repeat: 2 times, every 3 seconds.
SimpleJob says: group1.job1 executing at Sat Feb 13 00:09:08 CST 2021. 
SimpleJob says: group1.job2 executing at Sat Feb 13 00:09:08 CST 2021. 
SimpleJob says: group1.job2 executing at Sat Feb 13 00:09:11 CST 2021. 
SimpleJob says: group1.job2 executing at Sat Feb 13 00:09:14 CST 2021.

3、Cron触发器

本示例(CronTriggerExample)演示使用Cron触发器的Quartz调度功能。Cron表达式的介绍参考Cron表达式简介

  • SimpleJob.java
public class SimpleJob implements Job{

	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobKey jobKey = context.getJobDetail().getKey();
		System.out.printf("SimpleJob says: %s executing at %s. \n", jobKey, new Date());
	}

}
  • CronTriggerExample

Job1每10秒执行一次,Job2在每月12号和13号凌晨0:33执行一次:

public class CronTriggerExample {

	public static void main(String[] args) throws Exception {
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();

		// job1
		JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("job1", "group1").build();
		CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
				.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")).build();
		Date ft = scheduler.scheduleJob(job, trigger);
		System.out.printf("%s has been scheduled to run at: %s and repeat based on expression: %s.\n", job.getKey(), ft,
				trigger.getCronExpression());

		// job2
		job = JobBuilder.newJob(SimpleJob.class).withIdentity("job2", "group1").build();
		trigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group1")
				.withSchedule(CronScheduleBuilder.cronSchedule("0 33 0 12,13 * ?")).build();
		ft = scheduler.scheduleJob(job, trigger);
		System.out.printf("%s has been scheduled to run at: %s and repeat based on expression: %s.\n", job.getKey(), ft,
				trigger.getCronExpression());
		
		scheduler.start();
		try {
	      // wait 30 seconds to show jobs
	      Thread.sleep(30 * 1000);
	      // executing...
	    } catch (Exception e) {
	      //
	    }
		scheduler.shutdown(true);
		
		SchedulerMetaData metaData = scheduler.getMetaData();
		System.out.printf("Executed %s jobs.", metaData.getNumberOfJobsExecuted());
	}
}
  • 输出
group1.job1 has been scheduled to run at: Sat Feb 13 00:33:00 CST 2021 and repeat based on expression: 0/10 * * * * ?.
group1.job2 has been scheduled to run at: Sat Feb 13 00:33:00 CST 2021 and repeat based on expression: 0 33 0 12,13 * ?.
SimpleJob says: group1.job1 executing at Sat Feb 13 00:33:00 CST 2021. 
SimpleJob says: group1.job2 executing at Sat Feb 13 00:33:00 CST 2021. 
SimpleJob says: group1.job1 executing at Sat Feb 13 00:33:10 CST 2021. 
SimpleJob says: group1.job1 executing at Sat Feb 13 00:33:20 CST 2021. 
SimpleJob says: group1.job1 executing at Sat Feb 13 00:33:30 CST 2021. 
Executed 5 jobs.

4、Job参数和状态

本示例(Job Parameters and Job State)演示如何将运行时参数传递给Quartz作业(Job),以及如何维护作业中的状态。

  • Job

ColorJob除了实现Job接口外还需增加@PersistJobDataAfterExecution@DisallowConcurrentExecution注解:

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class ColorJob implements Job{

	public static final String FAVORITE_COLOR = "favorite color";
    public static final String EXECUTION_COUNT = "count";
	
    private int _counter = 1;
    
    public ColorJob() {
		// TODO Auto-generated constructor stub
	}
    
	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobKey jobKey = context.getJobDetail().getKey();
		JobDataMap data = context.getJobDetail().getJobDataMap();
		String favoriteColor = data.getString(FAVORITE_COLOR);
        int count = data.getInt(EXECUTION_COUNT);
        
        StringBuilder info = new StringBuilder();
        info.append("ColorJob: %s  executing at %s \n");
        info.append("  favorite color is %s \n");
        info.append("  execution count (from job map) is %s \n");
        info.append("  execution count (from job member variable) is %s \n\n");
        System.out.printf(info.toString(), jobKey, new Date(), favoriteColor, count, _counter);
        
        count++;
        data.put(EXECUTION_COUNT, count);
        _counter++;
	}

}
  • Application

Job1每2秒执行一次,重复3次;Job2每1秒执行一次,重复2次:

public class JobStateExample {

	public static void main(String[] args) throws Exception {
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();

		System.out.printf("current time: %s\n", new Date());
		// get a "nice round" time a few seconds in the future....
		Date startTime = DateBuilder.nextGivenSecondDate(null, 10);
		// job1
		JobDetail job = JobBuilder.newJob(ColorJob.class).withIdentity("job1", "group1").build();
		SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(startTime)
				.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).withRepeatCount(3))
				.build();
		job.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "Green");
		job.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1);
		Date scheduleTime = scheduler.scheduleJob(job, trigger);
		System.out.printf("%s will run at: %s and repeat: %s times, every %s seconds\n", job.getKey(), scheduleTime,
				trigger.getRepeatCount(), trigger.getRepeatInterval() / 1000);

		// job2
		job = JobBuilder.newJob(ColorJob.class).withIdentity("job2", "group1").build();
		trigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group1").startAt(startTime)
				.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).withRepeatCount(2))
				.build();
		job.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "Red");
		job.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1);
		scheduleTime = scheduler.scheduleJob(job, trigger);
		System.out.printf("%s will run at: %s and repeat: %s times, every %s seconds\n\n", job.getKey(), scheduleTime,
				trigger.getRepeatCount(), trigger.getRepeatInterval() / 1000);

		scheduler.start();
		try {
			// wait five minutes to show jobs
			Thread.sleep(15 * 1000);
			// executing...
		} catch (Exception e) {
			//
		}
		scheduler.shutdown(true);
		
		SchedulerMetaData metaData = scheduler.getMetaData();
		System.out.printf("Executed %s jobs.", metaData.getNumberOfJobsExecuted());
	}
}
  • 输出
current time: Sat Feb 13 13:17:24 CST 2021
group1.job1 will run at: Sat Feb 13 13:17:30 CST 2021 and repeat: 3 times, every 2 seconds
group1.job2 will run at: Sat Feb 13 13:17:30 CST 2021 and repeat: 2 times, every 1 seconds

ColorJob: group1.job1  executing at Sat Feb 13 13:17:30 CST 2021 
  favorite color is Green 
  execution count (from job map) is 1 
  execution count (from job member variable) is 1 

ColorJob: group1.job2  executing at Sat Feb 13 13:17:30 CST 2021 
  favorite color is Red 
  execution count (from job map) is 1 
  execution count (from job member variable) is 1 

ColorJob: group1.job2  executing at Sat Feb 13 13:17:31 CST 2021 
  favorite color is Red 
  execution count (from job map) is 2 
  execution count (from job member variable) is 1 

ColorJob: group1.job1  executing at Sat Feb 13 13:17:32 CST 2021 
  favorite color is Green 
  execution count (from job map) is 2 
  execution count (from job member variable) is 1 

ColorJob: group1.job2  executing at Sat Feb 13 13:17:32 CST 2021 
  favorite color is Red 
  execution count (from job map) is 3 
  execution count (from job member variable) is 1 

ColorJob: group1.job1  executing at Sat Feb 13 13:17:34 CST 2021 
  favorite color is Green 
  execution count (from job map) is 3 
  execution count (from job member variable) is 1 

ColorJob: group1.job1  executing at Sat Feb 13 13:17:36 CST 2021 
  favorite color is Green 
  execution count (from job map) is 4 
  execution count (from job member variable) is 1 

Executed 7 jobs.

5、异常处理

本示例(Dealing with Job Exceptions)演示如何处理作业执行异常;Quartz中的作业允许抛出JobExecutionExceptions异常,当抛出此异常时可以指定Quartz采取什么操作。

  • BadJobOne.java
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BadJobOne implements Job{

	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobKey jobKey = context.getJobDetail().getKey();
	    JobDataMap dataMap = context.getJobDetail().getJobDataMap();

	    int denominator = dataMap.getInt("denominator");
	    System.out.printf("---%s executing at %s with denominator %s\n", jobKey, new Date(), denominator);
	    try {
	    	int calculation = 6666 / denominator;
	    }catch (Exception e) {
	    	System.out.printf("---Error in job %s\n", jobKey);
	    	JobExecutionException je = new JobExecutionException(e);
	    	//修复分母,下次运行此作业时不会再失败
	    	dataMap.put("denominator", 1);
	    	//立即重新激活作业
	    	je.setRefireImmediately(true);
	    	throw je;
		}
	    
	    System.out.printf("---%s completed at %s\n", jobKey, new Date());
	}

}
  • BadJobTwo
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BadJobTwo implements Job{

	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobKey jobKey = context.getJobDetail().getKey();
		System.out.printf("***%s executing at %s\n", jobKey, new Date());
		
		try {
			int zero = 0;
			int calculation = 5555 / zero;
		}catch(Exception e) {
			System.out.printf("***Error in job %s\n", jobKey);
			JobExecutionException je = new JobExecutionException(e);
			//Quartz将自动取消与该作业相关的所有触发器,因此它不会再运行
			je.setUnscheduleAllTriggers(true);
			throw je;
		}
		
		System.out.printf("***%s completed at %s\n", jobKey, new Date());
	}

}
  • JobExceptionExample.java

job1每5秒运行一次,此job将会抛出异常并修复错误后立即重新激活执行;job2每3秒运行一次,此job在抛出异常后不会再激活执行。

public class JobExceptionExample {

	public static void main(String[] args) throws Exception {
		SchedulerFactory sf = new StdSchedulerFactory();
		Scheduler scheduler = sf.getScheduler();
		System.out.printf("current time: %s\n", new Date());
		Date startTime = DateBuilder.nextGivenSecondDate(null, 10);

		// job1
		JobDetail job = JobBuilder.newJob(BadJobOne.class).withIdentity("badJobOne", "group1")
				.usingJobData("denominator", 0).build();
		SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(startTime)
				.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();
		Date scheduleTime = scheduler.scheduleJob(job, trigger);
		System.out.printf("%s will run at: %s and repeat: %s times, every %s seconds\n", job.getKey(), scheduleTime,
				trigger.getRepeatCount(), trigger.getRepeatInterval() / 1000);

		// job2
		job = JobBuilder.newJob(BadJobTwo.class).withIdentity("badJobTwo", "group1").build();
		trigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group2").startAt(startTime)
				.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()).build();
		scheduleTime = scheduler.scheduleJob(job, trigger);
		System.out.printf("%s will run at: %s and repeat: %s times, every %s seconds\n", job.getKey(), scheduleTime,
				trigger.getRepeatCount(), trigger.getRepeatInterval() / 1000);

		scheduler.start();
		try {
			// sleep for 15 seconds
			Thread.sleep(15 * 1000);
		} catch (Exception e) {
			//
		}
		scheduler.shutdown(false);
		
		SchedulerMetaData metaData = scheduler.getMetaData();
		System.out.printf("Executed %s jobs.", metaData.getNumberOfJobsExecuted());
	}
}
  • 输出
current time: Sat Feb 13 14:05:37 CST 2021
group1.badJobOne will run at: Sat Feb 13 14:05:40 CST 2021 and repeat: -1 times, every 5 seconds
group1.badJobTwo will run at: Sat Feb 13 14:05:40 CST 2021 and repeat: -1 times, every 3 seconds
---group1.badJobOne executing at Sat Feb 13 14:05:40 CST 2021 with denominator 0
---Error in job group1.badJobOne
***group1.badJobTwo executing at Sat Feb 13 14:05:40 CST 2021
---group1.badJobOne executing at Sat Feb 13 14:05:40 CST 2021 with denominator 1
---group1.badJobOne completed at Sat Feb 13 14:05:40 CST 2021
***Error in job group1.badJobTwo
---group1.badJobOne executing at Sat Feb 13 14:05:45 CST 2021 with denominator 1
---group1.badJobOne completed at Sat Feb 13 14:05:45 CST 2021
---group1.badJobOne executing at Sat Feb 13 14:05:50 CST 2021 with denominator 1
---group1.badJobOne completed at Sat Feb 13 14:05:50 CST 2021
Executed 5 jobs.
参考资料:

Overview

Quartz Documentation

Quartz Quick Start Guide

Examples