Spring Boot 和 Quartz
Quartz 介绍
Quartz 是 OpenSymphony 开源组织在 Job scheduling 领域又一个开源项目,是完全由 Java 开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中,它提供了巨大的灵活性而不牺牲简单性。
当定时任务愈加复杂时,使用 Spring 注解 @Schedule 已经不能满足业务需要。
Quartz 的优点
丰富的 Job 操作 API;
支持多种配置;
Spring Boot 无缝集成;
支持持久化;
支持集群;
Quartz 还支持开源,是一个功能丰富的开源作业调度库,可以集成到几乎任何 Java 应用程序中。
Quartz 体系结构
若要明白 Quartz 怎么用首先要了解 Job(任务)、JobDetail(任务信息)、Trigger(触发器)和Scheduler(调度器)这4个核心的概念。
Job:是一个接口,只定义一个方法 execute(JobExecutionContext context),在实现接口的 execute 方法中编写所需要定时执行的 Job(任务),JobExecutionContext 类提供了调度应用的一些信息。Job 运行时的信息保存在 JobDataMap 实例中。
JobDetail:Quartz 每次调度 Job 时,都重新创建一个 Job 实例,所以它不直接接受一个 Job 的实例,相反它接收一个 Job 实现类(JobDetail,描述 Job 的实现类及其他相关的静态信息,如 Job 名字、描述、关联监听器等信息),以便运行时通过 newInstance() 的反射机制实例化 Job。
Trigger:是一个类,描述触发 Job 执行的时间触发规则。主要有 SimpleTrigger 和 CronTrigger 这两个子类。当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择;而 CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案,如工作日周一到周五的 15:00~16:00 执行调度等。
Scheduler:调度器就相当于一个容器,装载着任务和触发器。该类是一个接口,代表一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中拥有各自的组及名称,组及名称是 Scheduler 查找定位容器中某一对象的依据,Trigger 的组及名称必须唯一, JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。Scheduler 定义了多个接口方法,允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。
四者其关系如下图所示:
Job 为作业的接口,为任务调度的对象;JobDetail 用来描述 Job 的实现类及其他相关的静态信息;Trigger 做为作业的定时管理工具,一个 Trigger 只能对应一个作业实例,而一个作业实例可对应多个触发器;Scheduler 做为定时任务容器,是 quartz 最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个 Scheduler 都存有 JobDetail 和 Trigger 的注册,一个 Scheduler 中可以注册多个 JobDetail 和多个 Trigger。
Spring Boot 和 Quartz
网上关于 Quartz 集成 Spring Boot 的案例很多很杂,这是因为 Spring Boot 1.0 并没有提供 Quartz 的 Starter 支持包,因此大家都是各显神通用自己的方案来集成。直到现在 Spring Boot 2.0 的出现改变了这个情况,Spring Boot 2.0 提供了 spring-boot-starter-quartz 组件集成 Quartz,让我们在项目中使用 Quartz 变的异常简单,特别是对比 1.0 大家手动的集成方案,才会真正的感受到 Spring Boot 简洁开发的魅力。
配置 pom.xml
添加 spring-boot-starter-quartz 组件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-quartz</artifactId>
</dependency>
简单示例
配置完成之后我们先来做一个最简单的示例,使用 Quartz 定时输出 你好 Jacky。
首先定义一个 Job 需要继承 QuartzJobBean,示例中 Job 定义一个变量 Name,用于在定时执行的时候传入。
package com.example.quartz.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Component
public class SampleJob extends QuartzJobBean {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println(String.format("你好 %s!",this.name));
}
}
接下来构建 JobDetail,并且构建时传入 name 属性的值,构建 JobTrigger 和 scheduleBuilder,最后使用 Scheduler 启动定时任务。
package com.example.quartz.schedule;
import com.example.quartz.job.SampleJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SampleSchedule {
@Bean
public JobDetail sampleJobDetail() {
return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob")
.usingJobData("name", "Jacky").storeDurably().build();
}
@Bean
public Trigger sampleJobTrigger() {
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever();
return TriggerBuilder.newTrigger().forJob(sampleJobDetail())
.withIdentity("sampleTrigger").withSchedule(scheduleBuilder).build();
}
}
JobBuilder 无构造函数,所以只能通过 JobBuilder 的静态方法 newJob(Class jobClass) 生成 JobBuilder 实例。
withIdentity 方法可以传入两个参数withIdentity(String name,String group)来定义 TriggerKey,也可以不设置,像上文示例中会自动生成一个独一无二的 TriggerKey 用来区分不同的 trigger。
启动项目后每隔两秒输出:你好 Jacky!
CronSchedule 示例
CronSchedule 可以设置更灵活的使用方式,定时设置可以参考上面的 cron 表达式。
首先定义两个 Job:
package com.example.quartz.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Component
public class CronJob1 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("CronJob 1 正在运行...........");
}
}
package com.example.quartz.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Component
public class CronJob2 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("CronJob 2 正在运行...........");
}
}
按照使用 Quartz 的逻辑,构建 jobDetail、CronTrigger,最后使用 scheduler 关联 jobDetail 和 CronTrigger。scheduleJob1 设置每间隔 8 秒执行一次。
CronScheduleBuilder.cronSchedule(“0/8 * * * * ?"),按照 cron 表达式设置定时任务的执行周期。
scheduleJob 2 的内容和 scheduleJob 1 基本一致,时间设置为间隔 16 秒执行一次。
使用 scheduler 启动两个定时任务,如下:
package com.example.quartz.schedule;
import com.example.quartz.job.CronJob1;
import com.example.quartz.job.CronJob2;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;
@Component
public class CronSchedule {
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
public void scheduleJobs() throws SchedulerException {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduleJob1(scheduler);
scheduleJob2(scheduler);
}
private void scheduleJob1(Scheduler scheduler) throws SchedulerException{
JobDetail jobDetail = JobBuilder.newJob(CronJob1.class) .withIdentity("job1", "group1").build();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/8 * * * * ?");
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") .withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail,cronTrigger);
}
private void scheduleJob2(Scheduler scheduler) throws SchedulerException{
JobDetail jobDetail = JobBuilder.newJob(CronJob2.class) .withIdentity("job2", "group2").build();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/16 * * * * ?");
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger2", "group2") .withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail,cronTrigger);
}
}
何时触发定时任务
启动时调用 scheduleJobs() 来启动定时任务
package com.example.quartz;
import com.example.quartz.schedule.CronSchedule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class CronStartup implements CommandLineRunner {
@Autowired
public CronSchedule cronSchedule;
@Override
public void run(String... args) throws Exception {
cronSchedule.scheduleJobs();
System.out.println("------多个定时任务启动--------");
}
}
定时一个 Runner,继承 CommandLineRunner 并重新 run 方法,在 run 方法中调用 scheduleJobs() 来启动定时任务。
启动项目后每隔 8秒输出 job 1 内容,每隔 16 秒输出 job 2 内容,再加上上面示例每两秒输出的 你好 Jacky,输出内容如下:
总结
通过今天的示例可以看出,如果仅需要执行简单定时任务,就可以使用 Spring Boot 自带 Scheduled,可以非常简单和方便的使用。但如果需要在项目中执行大量的批任务处理时,可以采用 Quartz 来解决,Spring Boot 2.0 中提供了对 Quartz 的支持,让我们在项目使用的过程中更加的灵活简洁。