Quartz是什么

Quartz是一个功能强大的开源任务调度库,几乎可以集成到任何Java应用程序中,无论是超小型的独立应用还是超大型电子商务系统。

它常用于企业级应用中:

  • Driving Process Workflow:当新订单下达,可以安排一个30分钟内触发的任务,检查订单状态。
  • System Maintenance:安排每个工作日晚上11点将数据库内容转储到文件的任务。
  • Providing reminder services:提供提醒服务。

Quartz还支持集群模式和对JTA事务。

Quartz中的重要API及概念

超重要API

  • Scheduler 和调度程序交互的主要API
    • 生命周期从SchedulerFactoru创建它开始,到调用shutdown方法结束。
    • 一旦Scheduler创建,任何关于scheduling相关的事,他都为所欲为:添加、删除、列出所有的Jobs和triggers、暂停触发器等。
    • 在start方法之前,不会做任何事情。
  • Job 你希望被调度器调度的任务组件接口。
    • 当Job的触发器触发时,调度程序的工作线程将调用execute方法。
    • 该方法接收一个JobExecutionContext 对象,为Job实例提供了丰富的运行时环境信息,比如:scheduler、trigger、jobDataMap、job、calendar、各种time等。
  • JobDetail 用于定义任务。
    • JobDetail对象由Quartz客户端在将job加入Scheduler提供,也就是你的程序。
    • 它包含了不同为job设置的属性,还有可以用来为job储存状态信息的JobDataMap。
    • 注意它和Job的区别,它实际上是Job实例的属性。【Job定义如何执行,JobDetail定义有何属性】
  • Trigger 触发任务执行。
    • 触发器可能具有与之关联的JobDataMap,以便于将特定于触发器触发的参数传递给Job。
    • Quartz提供了几种不同的触发器,SimpleTrigger和CronTrigger比较常用。
    • 如果你需要一次性执行作业或需要在给定的时间触发一个作业并重复执行N次且有两次执行间有延时delay,SimpleTrigger较为方便。
    • 如果你希望基于类似日期表触发执行任务,CronTrgger推荐使用。
  • JobBuilder 用于构建JobDetail的。
  • TriggerBuilder 用于构建Trigger的。

重要概念

  • Identity
    • 当作业和触发器在Quartz调度程序中注册时,会获得标识键。
    • JobKey和TriggerKey允许被置入group中,易于组织管理。
    • 唯一的,是name和group的组合标识。
  • JobDataMap
    • 是Map的实现,具有key-value相关操作,存储可序列化数据对象,供Job实例在执行时使用。
    • 可以使用usingJobData(key,value)在构建Jobdetail的时候传入数据,使用jobDetail.getJobDataMap()获取map。

Quartz设计理念:为什么设计Job和Trigger?

隔离schedule和schedule上执行的Job,优点是可见的:

可以独立于触发器创建作业并将其存储在作业调度程序中,并且许多触发器可以与同一作业相关联。这样的松耦合好处是什么?

  • 如果触发器过期,作业还可以保留在程序中,以便重新调度,而不必重新定义。
  • 如果你希望修改替换某个触发器,你不必重新定义其关联的作业。

最简单的Quartz使用案例

导入依赖

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

简单案例如下

public class QuartzTest {
	// 你需要在start和shutdown之间执行你的任务。
    public static void main(String[] args) {

        try {
            // 从工厂中获取Scheduler示例
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // 开始
            scheduler.start();

            // 定义Job,并将其绑定到HelloJob类中
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("job1", "group1") // name 和 group
                    .usingJobData("username", "天乔巴夏") // 置入JobDataMap
                    .usingJobData("age", "20")
                    .withDescription("desc-demo")
                    .build();

            // 触发Job执行,每40s执行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow() // 立即开始
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInSeconds(40)
                            .repeatForever())
                    .build();

            // 告诉 quartz 使用trigger来调度job
            scheduler.scheduleJob(job, trigger);
			// 关闭,线程终止
            scheduler.shutdown();

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

}

@Slf4j
@NoArgsConstructor
public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 从context中获取属性
        JobDetail jobDetail = context.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        JobKey key = jobDetail.getKey();
        Class<? extends Job> jobClass = jobDetail.getJobClass();
        String description = jobDetail.getDescription();

        String username = jobDataMap.getString("username");
        int age = jobDataMap.getIntValue("age");

        log.info("\nJobKey : {},\n JobClass : {},\n JobDesc : {},\n username : {},\n age : {}",
                key, jobClass.getName(), description, username, age);
    }
}

启动测试,打印日志如下:

01:23:12.406 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
01:23:12.414 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
01:23:12.430 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
01:23:12.430 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
01:23:12.432 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
01:23:12.433 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) '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.

01:23:12.433 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
01:23:12.433 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
01:23:12.434 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
01:23:12.434 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
01:23:12.443 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
01:23:12.445 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.job1', class=com.hyhwky.HelloJob
01:23:12.451 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
01:23:12.452 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.job1
01:23:12.452 [DefaultQuartzScheduler_Worker-1] INFO com.hyhwky.HelloJob -
JobKey : group1.job1,
 JobClass : com.hyhwky.HelloJob,
 JobDesc : desc-demo,
 username : 天乔巴夏,
 age : 20

我们可以看到quartz已经被初始化了,初始化配置如下,在org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar!\org\quartz\quartz.properties

# 调度器配置
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 #trigger 容忍时间60s

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

更多的配置:Quartz Configuration Reference

Job实例和JobDetail

Job和JobDetail

  • Job是正在执行的作业实例,JobDetail是作业定义。
  • 一个Job可以创建多个JobDetail,拥有不同的JobDataMap。

这样定义有什么好处呢?举个例子吧,假设现在你定义了一个类实现了Job接口,比如:SendEmailJob。如果你希望根据用户的姓名,选择指定的人发送,那么你可以通过JobDataMap绑定参数传递进JobDetail中,也就是说我们需要创建两个不同的JobDetail,比如:SendEmailToSummerday和SendEmailToTQBX,他们拥有各自独立的JobDataMap,实现更加灵活。

Job的State和Concurrency

@DisallowConcurrentExecution

该注解标注在Job类上,意思是不能并发执同一个JobDetail定义的多个实例,但可以同时执行多个不同的JobDetail定义的实例。

拿上面的例子继续举例,假设SendEmailJob标注了此注解,表明同一时间可以同时执行SendEmailToSummerday和SendEmailToTQBX,因为他们是不同的JobDetail,但是不能同时执行多个SendEmailToSummerday。

@PersistJobDataAfterExecution

该注解也标注在Job类上,告诉Scheduler正常执行完Job之后,重新存储更新一下JobDataMap。一般标注此注解的Job类应当考虑也加上@DisallowConcurrentExecution注解,以避免同时执行Job时出现JobDataMap存储的竞争。

Trigger常见使用

构建一个触发器,该触发器将立即触发,然后每五分钟重复一次,直到小时 22:00:


import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.DateBuilder.*:

 SimpleTrigger trigger = (SimpleTrigger) newTrigger()
    .withIdentity("trigger7", "group1")
    .withSchedule(simpleSchedule()
        .withIntervalInMinutes(5)
        .repeatForever())
    .endAt(dateOf(22, 0, 0))
    .build();

构建一个触发器,该触发器将在周三上午 10:42 触发,在系统默认值以外的时区中:

import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.DateBuilder.*:

 trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(cronSchedule("0 42 10 ? * WED")) // [秒] [分] [时] [月的第几天] [月] [一星期的第几天] [年(可选)]
    .inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
    .forJob(myJobKey)
    .build();

Quartz存储与持久化

Job存储器的类型:

具体的使用方法:http://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/tutorial-lesson-09.html

12-26 22:32