Spring Boot系列: 点击查看Spring Boot系列文章
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
Quartz 允许程序开发人员根据时间的间隔来调度作业。
Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。
1、Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)
2、JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
3、Trigger 代表一个调度参数的配置,什么时候去调。触发器Trigger最基本的功能是指定Job的执行时间,执行间隔,运行次数等。
4、Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
Quartz API的关键接口是:
Scheduler - 与调度程序交互的主要API。
Job - 希望由调度程序执行的任务作业。
JobDetail - 用于定义作业的实例。
Trigger(即触发器) - 定义执行给定作业的计划组件。
JobBuilder - 用于定义/构建JobDetail实例,用于定义作业的实例。
TriggerBuilder - 用于定义/构建触发器实例。
Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger。
注:Job和Trigger的创建和运行等操作都是通过Scheduler执行的
当Job的一个trigger被触发时,execute()方法由调度程序的一个工作线程调用。execute()方法会被scheduler的一个工作线程调用;execute()方法的JobExecutionContext对象中保存着该job运行时的一些信息 ,执行job的scheduler的引用,触发job的trigger的引用,JobDetail对象引用,以及一些其它信息。
JobDetail对象是在将job加入scheduler时,由客户端程序(你的程序)创建的。它包含job的各种属性设置,以及用于存储job实例状态信息的JobDataMap。
Trigger用于触发Job的执行。当你准备调度一个job时,你创建一个Trigger的实例,然后设置调度相关的属性。Trigger也有一个相关联的JobDataMap,用于给Job传递一些触发相关的参数。Quartz自带了各种不同类型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。
SimpleTrigger主要用于一次性执行的Job(只在某个特定的时间点执行一次),或者Job在特定的时间点执行,重复执行N次,每次执行间隔T个时间单位。CronTrigger在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等。
为什么既有Job,又有Trigger呢?很多任务调度器并不区分Job和Trigger。有些调度器只是简单地通过一个执行时间和一些job标识符来定义一个Job;其它的一些调度器将Quartz的Job和Trigger对象合二为一。在开发Quartz的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。
例如,Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以修改或者替换Trigger,而不用重新定义与之关联的Job。
1、导入依赖,导入的是springboot集成的quartz,这样我们使用起来可以方便很多。
<!--spring boot集成quartz-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2、我们可以在配置文件进行Quartz的相关配置,当然我们不配置也可以使用Quartz,只不过使用的是默认配置。大家根据自己的需求进行配置
spring:
#配置数据源
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/testquartz?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: password
quartz:
#持久化到数据库方式
job-store-type: jdbc
#初始化表结构
initialize-schema: embedded
properties:
org:
quartz:
scheduler:
instanceName: MyScheduler
instanceId: AUTO
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_
isClustered: true
clusterCheckinInterval: 10000
useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
4、定义两个个job,一个cron类型的,一个simple的类型的
//定义job类很简单,只需要在springboot中继承QuartzJobBean 类,然后覆写executeInternal方法即可
public class QuartzTestJob extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(QuartzTestJob.class);
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
logger.info("执行cron定时任务");
}
}
public class SimpleTestJob extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(SimpleTestJob.class);
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
logger.info("执行simple定时任务");
}
}
我们可以看一下QuartzJobBean的 源码:
其实QuartzJobBean就是一个实现Job接口 的类,它替我们实现了execute方法,然后替我们封装了一个executeInternal方法来表示执行方法
public abstract class QuartzJobBean implements Job {
/**
* This implementation applies the passed-in job data map as bean property
* values, and delegates to {@code executeInternal} afterwards.
* @see #executeInternal
*/
@Override
public final void execute(JobExecutionContext context) throws JobExecutionException {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(context.getScheduler().getContext());
pvs.addPropertyValues(context.getMergedJobDataMap());
bw.setPropertyValues(pvs, true);
}
catch (SchedulerException ex) {
throw new JobExecutionException(ex);
}
executeInternal(context);
}
/**
* Execute the actual job. The job data map will already have been
* applied as bean property values by execute. The contract is
* exactly the same as for the standard Quartz execute method.
* @see #execute
*/
protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
}
5、创建好任务之后,我们就可以创建一个配置类,进行JobDetail 和 Trigger 触发器的配置
@Configuration
public class QuartzConfig {
/**
* 创建JobDetail
* @return
*/
@Bean
JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
//设置我们的PrintTimeJob业务类
bean.setJobClass(QuartzTestJob.class);
//每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
JobDataMap map = new JobDataMap();
map.put("msg", "hello");
bean.setJobDataMap(map);
//指定作业的持久性,即是否应该保留它。为true,表示即使没有触发器指向这个job,也保留该job
bean.setDurability(true);
return bean;
}
@Bean
JobDetailFactoryBean simJobDetailFactoryBean() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
//设置我们的PrintTimeJob业务类
bean.setJobClass(SimpleTestJob.class);
//每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
JobDataMap map = new JobDataMap();
map.put("msg", "hello");
bean.setJobDataMap(map);
//指定作业的持久性,即是否应该保留它。为true,表示即使没有触发器指向这个job,也保留该job
bean.setDurability(true);
return bean;
}
/**
* 创建Trigger触发器
* @return
*/
@Bean
CronTriggerFactoryBean cronTriggerFactoryBean(){
CronTriggerFactoryBean cronTriggerFactoryBean=new CronTriggerFactoryBean();
// 设置cron表达式
cronTriggerFactoryBean.setCronExpression("0/3 * * * * ?");
// 设置JobDetail
cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean().getObject());
JobDataMap jobDataMap=new JobDataMap();
jobDataMap.put("msg","cron");
// 设置job的DataMap
cronTriggerFactoryBean.setJobDataMap(jobDataMap);
// 设置Trigger名称
cronTriggerFactoryBean.setName("cronTrigger");
return cronTriggerFactoryBean;
}
@Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean(){
SimpleTriggerFactoryBean simpleTriggerFactoryBean=new SimpleTriggerFactoryBean();
// 指定这个触发器的执行时间间隔。,单位为毫秒
simpleTriggerFactoryBean.setRepeatInterval(5000);
// 执行的次数,执行了这么多次后就不再执行
simpleTriggerFactoryBean.setRepeatCount(3);
// 设置JobDetail
simpleTriggerFactoryBean.setJobDetail(simJobDetailFactoryBean().getObject());
// 设置触发器的具体开始时间。
// simpleTriggerFactoryBean.setStartTime(new Date());
return simpleTriggerFactoryBean;
}
/**
* 创建Scheduler
* @return
*/
@Bean
SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
bean.setTriggers(cronTriggerFactoryBean().getObject(), simpleTriggerFactoryBean().getObject());
return bean;
}
}
在spring boot中,我们配置JobDetail 是使用JobDetailFactoryBean ,注册一个JobDetailFactoryBean 的bean,就相当于是配置了一个JobDetail 。
配置Trigger 触发器,我们可以选择使用SimpleTriggerFactoryBean还是CronTriggerFactoryBean ,CronTriggerFactoryBean 类型触发器一般用于定时任务,因为它可以使用cron表达式,使用起来更加灵活。而一般类型的job工作,直接使用SimpleTriggerFactoryBean即可。配置相应的bean即可。
我们前面说过,Job和Trigger的操作都是通过Scheduler来执行,所以最后我们还要配置一个SchedulerFactoryBean的bean,相当于使用这个 Scheduler来调度job和trigger。
除了FactoryBean,我们还可以使用JobBuilder和TriggerBuilder来创建job和trigger,这也是官方的创建方法
@Configuration
public class BuilderQuartzConfig {
// 定义JobDetail,使用JobBuilder的build方法创建JobDetail
@Bean
public JobDetail builderJobDetail(){
return JobBuilder
.newJob(BuilderJob.class)//通过class引入job类
.withIdentity("builderJob","builderGroup")//定义任务名称和分组
.storeDurably()//当job没有触发器绑定时,保留该job,不调用该方法,则会抛弃job
.build();
}
//定义触发器,使用TriggerBuilder的build方法
@Bean
public Trigger orderTrigger() {
/*SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMinutes(1) //定义时间周期
.repeatForever();*/
//定义调度器
//"0/20 * * * * ?" 表示每隔20秒执行一次,withMisfireHandlingInstructionDoNothing为错失不补偿
CronScheduleBuilder scheduleBuilder
= CronScheduleBuilder.cronSchedule("0/20 * * * * ?");
return TriggerBuilder
.newTrigger()
.forJob(builderJobDetail())//关联我们定义的job
.withIdentity("builderTrigger")//定义名称
.withSchedule(scheduleBuilder)//关联Schedule调度程序
.build();
}
}
大家可以自行选择使用哪种方法来定义
启动项目,我们的调度任务就会开启执行了。