quartz 动态添加job_入门Java开源任务调度框架-Quartz(前篇)
1)Quartz是什么Quartz是一款Java编写的开源任务调度框架,同时它也是Spring默认的任务调度框架。它的作用其实类似于Java中的Timer定时器以及JUC中的ScheduledExecutorService调度线程池,当然Quartz作为一个独立的任务调度框架无疑在这方面表现的更为出色,功能更强大,能够定义更为复杂的执行规则。Quartz中主要用到了:Builder建造者模式、Fa
1)Quartz是什么
Quartz是一款Java编写的开源任务调度框架,同时它也是Spring默认的任务调度框架。它的作用其实类似于Java中的Timer定时器以及JUC中的ScheduledExecutorService调度线程池,当然Quartz作为一个独立的任务调度框架无疑在这方面表现的更为出色,功能更强大,能够定义更为复杂的执行规则。Quartz中主要用到了:Builder建造者模式、Factory工厂模式以及组件模式,我们要知道Quartz是如何调度的,需要知道三个概念:任务(Job,我们需要将具体的业务逻辑写到实现了Job接口的实现类中)、触发器(Trigger,它定义了任务的执行规则),最后是调度器(Scheduler,通过传入的任务Job和触发器Trigger,以指定的规则执行任务)。
要想使用Quartz,我们先来创建一个简单的maven项目,同时有必要了解Quartz中的一些基本的(概念)接口和类。
2)创建一个简单的Maven样例项目
为了方便验证代码,建议创建一个简单的maven管理的普通Java项目(只填写必要信息,不需要勾选项目骨架)。
这里我们先勾选最主要的Quartz依赖与maven编译插件:
<dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<target>1.8</target>
<source>1.8</source>
</configuration>
</plugin>
</plugins>
</build>
Quartz使用的是slf4j日志门面,但是还没有具体的实现;为了更直观的看到任务的执行时间,这里导入Logback的日志实现,在pom文件中新增依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
默认的日志格式不是很好,我们在maven项目中的resources目录下直接放置一个名为logback.xml的文件,这里只要配置控制台的输出即可:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false" >
<!-- 日志级别 -->
<property name="logLevel" value="INFO"/>
<!-- 异步缓冲队列的深度,该值会影响性能.默认值为256 -->
<property name="queueSize" value="512" />
<!-- LOGGER PATTERN 配置化输出格式 -->
<property name="logPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5level] %logger - %msg%n"/>
<!-- 控制台打印日志的相关配置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式 -->
<encoder>
<charset>UTF-8</charset>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<root level="${logLevel}">
<appender-ref ref="STDOUT" />
</root>
</configuration>
下面是对Quartz使用中的几个重要接口和类的说明与使用,它们分别是:Job、JobExecutionContext、JobDetail、JobBuilder、Trigger、TriggerBuilder、JobDataMap以及Scheduler。
3)Job任务接口(具体任务的执行入口)
Job接口很简单,只有一个execute方法,这是我们自己的具体业务逻辑的入口,类似TimerTask的run方法:
public interface Job {
void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException;
}
要创建一个任务我们需要编写一个实现该接口的具体任务类:
public class QuartzJob implements Job {
public void execute(JobExecutionContext jobExecutionContext)
throws JobExecutionException {
// 任务逻辑代码
}
}
但是Job并不能直接被调度器使用,它需要通过JobDetail绑定后传递给Scheduler。
要特别注意的是:在任务类中我们必须要有一个无参构造器,因为Quartz是通过反射机制实例化我们的任务类的;一个Job可以对应多个Trigger,每当调度器要执行Job的execute方法前,会创建一个Job实例,而当调用完成后,该实例会被垃圾收集器回收。
4)JobDetail任务接口
见名知义,它承载了Job对象的详细信息,同时保存(绑定)Job对象,因为Job接口只有一个execute方法,方法中只能有我们具体的业务逻辑方法,而我们一个进程中可能存在多个Job对象实例,所以我们有必要给它命名和分组。所以就有了JobDetail,我们实际传递给调度器的也是JobDetail而不是Job对象。
JobDetail描述了Job对象的基本信息,主要包含四个重要的属性:name(Job的名称)、group(Job的组名称)、jobClass(Job对应的类)以及jobDataMap(存储一些用户自定义的信息或对象)。在Scheduler中Job的名称name和组group组合必须是唯一的。
这是JobDetail接口的方法:
其中对应的属性设置可通过JobBuilder方法设置,下面对几个比较重要的属性进行说明:
java public boolean isConcurrentExectionDisallowed();
是否禁止并发运行):如果该属性为true,表示任务不是并发运行的,可通过在Job实现类上标注@DisallowConcurrentExecution注解设置。
java public boolean isDurable();
任务是否是可持续的):如果一个任务不是可持续的,则当没有触发器关联它的时候,Quartz会从scheduler中删除它。
java public boolean isPersistJobDataAfterExecution();
是否在任务执行后持久化Job数据):true持久化,false不做操作;可通过在Job实现类上标注@PersistJobDataAfterExecution注解设置。
java public boolean requestsRecovery();
是否能请求恢复):如果一个任务请求恢复,一般是该任务执行期间发生了系统崩溃或者其他关闭进程的操作,当服务再次启动的时候,会再次执行该任务。这种情况下,JobExecutionContext.isRecovering()会返回true。
调度器需要借助JobDetail对象来添加Job实例,Job接口的实例要通过JobBuilder类构建(Builder建造者模式):
public class QuartzScheduler {
public static void main(String[] args) throws SchedulerException {
// 构建一个JobDetail实例,通过newJob方法绑定QuartzJob类,之后指定Job的名称和组名,但这不是必须的
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class)
.withIdentity("job1", "group1").build();
}
}
5)JobExecutionContext任务上下文接口
可以看到在上面Job接口的execute方法中有一个参数JobExecutionContext,这其实也是一个接口;当sheduler调用一个Job时,就会将JobExecutionContext传递给Job的execute方法,Job对象里能通过JobExecutionContext对象访问到Quartz运行时的环境和Job本身的一些信息如Scheduler、Trigger、Calendar以及JobDetail等等一些相关的对象信息,还有一个很重要的类型JobDataMap,它可以以Map(键值对)的形式传递我们的一些需要的信息(下面再细说)。除此之外JobExecutionContext还有很多其他的一些获取信息的方法,这里不再细讲,具体查看接口的源码即可。
6)Trigger触发器(规则)接口
如果说JobDetail(或Job)代表的是具体的任务,那么Trigger就是这些任务的执行规则。比如你需要任务什么时候开始执行,以及执行的频度和次数,都是通过这个接口的实例来描述的。多个触发器可以指向同一个任务,但一个触发器只能指向一个任务。
Trigger接口主要描述了一个任务(Job)的调度优先级、开始时间、结束时间、Calendar的名称等,此外里面还有两个比较重要的类型:JobKey和TriggerKey,这两个类都是org.quartz.utils.Key的子类型,Key类型中仅有的两个字段是name和group。毫无疑问,JobKey和Trigger分别描述的是Job和Trigger的名称和组。
Quartz中的Trigger的具体实现类如下,其中最常用的还是CronTriggerImpl和SimpleTriggerImpl:
SimpleTriggerImpl主要是一些简单的执行规则,而CronTriggerImpl则更为灵活强大,能够胜任更为复杂的规则。
同样,跟JobDetail类似,Trigger接口的实例要通过TriggerBuilder来构造:
public class QuartzScheduler {
public static void main(String[] args) throws SchedulerException {
// 构建一个JobDetail实例...
// 构建一个Trigger,指定Trigger名称和组,规定该Job立即执行,且两秒钟重复执行一次
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.startNow() // 执行的时机,立即执行
.withIdentity("trigger1", "group1") // 不是必须的
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever()).build(); // build之后返回的类型是SimpleTrigger,具体的类型是SimpleTriggerImpl
}
}
上面最重要的是withSchedule这个方法,它制定了任务的执行规则,这里使用到了SimpleScheduleBuilder来构建了一个简单的任务调度规则,最终导致build之后的Trigger实例属于SimpleTrigger类型。
我们有必要看看SimpleScheduleBuilder这个类,通过继承结构可以看到它属于ScheduleBuilder这个类的子类,查看ScheduleBuilder的继承树:
看到CronScheduleBuilder是不是有一种熟悉的味道?上面我说了Trigger只是一个接口,而CronTriggerImpl是很常用的一个具体类型,而CronScheduleBuilder就是用来构建这种规则的,在withSchedule方法中使用CronScheduleBuilder,那么调用build方法之后返回的将是CronTrigger(注意这只是接口类型,对应具体类型是CronTriggerImpl)。
Trigger这里用了较多篇幅,这无可厚非,因为这是任务调度中最核心的,使用任务调度框架就是因为它能根据规则执行我们的代码。
7)JobDataMap数据存储类
通过查看JobDetail、Trigger及JobExecutionContext的源码可以发现,他们中都存在JobDataMap这个类型,它是以Map的形式存储我们的一些自定义数据的。当Job对象的execute方法被调用时,JobDataMap会通过JobExecutionContext传递给execute方法,它可以用来装载任何可序列化的数据对象。JobDataMap实现了Java中的Map接口,提供了一些自己的方法来存储数据。
这是JobDataMap的继承树:
可以看到JobDataMap是DirtyFlagMap的子类,而DirtyFlagMap实际实现了Java中的java.util.Map类型:
// DirtyFlagMap是java.util.Map接口的子类
public class DirtyFlagMap<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { }
一句话:把它当Java中的map来用就对了!
8)Scheduler任务调度器
任务和调度规则都有了,那么谁来调用他们?这就是任务调度器(Scheduler)的工作了。
任务调度器(Scheduler)也是一个接口,它定义了调度任务中的基本操作骨架,既然是接口那就会有具体实现,这是Scheduler的实现结构:
那如何获取Scheduler的实例?可以通过SchedulerFactory这个调度器构建工厂的接口子工厂实例来完成,它有两个具体的工厂实现:
一般我们使用的是StdSchedulerFactory工厂来获取Scheduler实例,因为它可以使用配置文件方式配置参数,而DirectSchedulerFactory则是使用硬编码(通过调用方法)的方式来设置参数;下面下面先说StdSchedulerFactory工厂获取Scheduler实例的方法(`DirectSchedulerFactory在后篇补充):
// StdSchedulerFactory
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
这里返回的是一个Scheduler类型,StdScheduler是Scheduler的子类型。既然所有的组件都集齐了,我们就可以实际来使用Quartz完成自定义的任务了,
首先创建一个Quartz任务,任务中从JobExecutionContext中获取到了JobDetail和Trigger中的JobDataMap,并从中取到了客户端QuartzScheduler中传入的数据:
public class QuartzJob implements Job {
public void execute(JobExecutionContext jobExecutionContext)
throws JobExecutionException {
// 获取JobDetail中的JobDataMap
JobDataMap jobDetailDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
// 获取Trigger中的JobDataMap
JobDataMap triggerDataMap = jobExecutionContext.getTrigger().getJobDataMap();
log.info(jobDetailDataMap.get("message"));
log.info(triggerDataMap.get("number"));
}
}
创建Quartz客户端,构建JobDetail和Trigger并使用Scheduler开始任务调度(这里要注意的是Scheduler实例创建后处于“待机”状态,所以别忘了调用start方法启动调度器,否则任务是不会执行的!):
public class QuartzScheduler {
public static void main(String[] args) throws SchedulerException {
// 创建一个JobDetail实例
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class)
// 指定JobDetail的名称和组名称
.withIdentity("job1", "group1")
// 使用JobDataMap存储用户数据
.usingJobData("message", "JobDetail传递的文本数据").build();
// 创建一个SimpleTrigger,规定该Job立即执行,且两秒钟重复执行一次
SimpleTrigger trigger = TriggerBuilder.newTrigger()
// 设置立即执行,并指定Trigger名称和组名称
.startNow().withIdentity("trigger1", "group1")
// 使用JobDataMap存储用户数据
.usingJobData("number", 128)
// 设置运行规则,每隔两秒执行一次,一直重复下去
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever()).build();
// 得到Scheduler调度器实例
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.scheduleJob(jobDetail, trigger); // 绑定JobDetail和Trigger
scheduler.start(); // 开始任务调度
}
}
可以看到程序正确的执行了,也读取到了JobDetail和Trigger中传递的数据并打印到了控制台:
不知你是否觉得在QuartzJob类中的获取数据的方法有点繁琐?既要拿到JobDetail中的JobDataMap也要获取Trigger的,有没有简单点的方法呢?其实也很简单:
@Slf4j
public class QuartzJob implements Job {
public void execute(JobExecutionContext jobExecutionContext)
throws JobExecutionException {
// 获取JobDetail与Trigger合并后的JobDataMap
JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
log.info(mergedJobDataMap.get("message"));
log.info(mergedJobDataMap.get("number"));
}
}
执行结果和前面完全一致,但代码是不是清爽了很多?JobDetail和Trigger中的数据都被合并到一个JobDataMap中了,简洁是简洁了,但注意不要出现数据键重名的情况,否则会发生数据覆盖!
9)结语
本文只介绍了Quartz的简单使用方法,即便如此也用了很大的篇幅,更多内容将在下篇讲述,移步《入门Java开源任务调度框架-Quartz(后篇)》了解更多!
更多推荐


所有评论(0)