Quartz是实现定时任务的利器,Quartz主要有四个组成部分,分别是:

1. Job(任务):包含具体的任务逻辑;

2. JobDetail(任务详情):是对Job的一种详情描述;

3. Trigger(触发器):负责管理触发JobDetail的机制;

4. Scheduler(调度器):负责Job的执行。

有两种方式可以实现Spring Boot与Quartz的整合:

一、使用Spring提供的工厂类

spring-context.jar和spring-context-support.jar类库提供了一些org.quartz包的扩展,使用这些扩展并通过注入bean的方式可以实现与Quartz的整合。

1. pom.xml

添加依赖:

<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>

Spring Boot 2.0.4指定的quartz.jar的版本是2.3.0

2. Job

首先定义一个执行器:

import java.util.Date;

import devutility.internal.text.format.DateFormatUtils;

public class Executor {
public static void execute(String name, long costMillis) {
Date startDate = new Date();
Date endDate = new Date(startDate.getTime() + costMillis); StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("[%s]: ", DateFormatUtils.format(startDate, "yyyy-MM-dd HH:mm:ss:SSS")));
stringBuilder.append(String.format("%s executing on %s, ", name, Thread.currentThread().getName()));
stringBuilder.append(String.format("will finish at %s.", DateFormatUtils.format(endDate, "yyyy-MM-dd HH:mm:ss:SSS")));
System.out.println(stringBuilder.toString()); try {
Thread.sleep(costMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

定义Job:

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component; import devutility.test.app.quartz.common.Executor; @Component
@EnableScheduling
public class SpringJobs {
public void job1() {
Executor.execute("Job1", 5000);
} public void job2() {
Executor.execute("Job2", 6000);
}
}

@EnableScheduling注解是必须要有的。

3. JobDetail

 package devutility.test.app.quartz.config;

 import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean; import devutility.test.app.quartz.jobs.SpringJobs; @Configuration
public class SpringJobConfiguration {
@Bean
public MethodInvokingJobDetailFactoryBean jobDetailFactory1(SpringJobs springJobs) {
MethodInvokingJobDetailFactoryBean jobDetailFactory = new MethodInvokingJobDetailFactoryBean();
jobDetailFactory.setName("Spring-Job1");
jobDetailFactory.setGroup("Spring-Group"); jobDetailFactory.setTargetObject(springJobs);
jobDetailFactory.setTargetMethod("job1"); jobDetailFactory.setConcurrent(false);
return jobDetailFactory;
} @Bean
public MethodInvokingJobDetailFactoryBean jobDetailFactory2(SpringJobs springJobs) {
MethodInvokingJobDetailFactoryBean jobDetailFactory = new MethodInvokingJobDetailFactoryBean();
jobDetailFactory.setName("Spring-Job2");
jobDetailFactory.setGroup("Spring-Group"); jobDetailFactory.setTargetObject(springJobs);
jobDetailFactory.setTargetMethod("job2"); jobDetailFactory.setConcurrent(true);
return jobDetailFactory;
}

MethodInvokingJobDetailFactoryBean是来自spring-context-support.jar的一个工厂类,它实现了FactoryBean<JobDetail>接口,完成了对具体job的封装。

21行指定具体的任务是我们在2中定义的SpringJobs,22行指定我们使用SpringJobs中的job1方法作为定时任务的具体业务实现。

注意24和37行,如果一个任务每隔5秒执行一次,但是每次需要执行10秒,那么它有两种执行方式,串行(执行完上一个再开启下一个)和并行(间隔时间到了就开始并行执行下一个),24和37就分别设置成了并行和串行执行。

4. Trigger

Quartz支持多种trigger,其中配置最为灵活的一种当属CronTrigger,它属于表达式类型的配置方式,有关cron表达式的配置请参考

 package devutility.test.app.quartz.config;

 import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean; import devutility.test.app.quartz.jobs.SpringJobs; @Configuration
public class SpringJobConfiguration {
@Bean
public CronTriggerFactoryBean cronTriggerFactory1(@Qualifier("jobDetailFactory1") MethodInvokingJobDetailFactoryBean jobDetailFactory1) {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setName("cronTriggerFactoryForJobDetailFactory1");
cronTriggerFactoryBean.setJobDetail(jobDetailFactory1.getObject());
cronTriggerFactoryBean.setCronExpression("0/3 * * * * ?");
return cronTriggerFactoryBean;
} @Bean
public CronTriggerFactoryBean cronTriggerFactory2(@Qualifier("jobDetailFactory2") MethodInvokingJobDetailFactoryBean jobDetailFactory2) {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setName("cronTriggerFactoryForJobDetailFactory2");
cronTriggerFactoryBean.setJobDetail(jobDetailFactory2.getObject());
cronTriggerFactoryBean.setCronExpression("0/4 * * * * ?");
return cronTriggerFactoryBean;
}

本例使用的Cron表达式非常简单,分别是每隔3秒和每隔4秒执行一次。

5. Scheduler

     @Bean
public SchedulerFactoryBean schedulerFactory1(Trigger cronTriggerFactory1, Trigger cronTriggerFactory2) {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setStartupDelay(2);
schedulerFactoryBean.setTriggers(cronTriggerFactory1, cronTriggerFactory2);
return schedulerFactoryBean;
}

行4指明系统启动之后需要延迟2秒执行;

行5用于注册需要执行的触发器;

SchedulerFactoryBean还有一个叫autoStartup的属性,用于指明任务在系统启动之后是否立即执行,默认是true。

6. 测试

由此可见,Job1是按照我们的预期串行执行的。

Job2则是并行执行的。

二、使用org.quartz原生类和方法

1. pom.xml,只需要添加quartz

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

2. 定义一个Scheduler类型的Bean

package devutility.test.app.quartz.config;

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class QuartzConfiguration {
@Bean
public Scheduler scheduler() throws SchedulerException {
return StdSchedulerFactory.getDefaultScheduler();
}
}

3. Job

package devutility.test.app.quartz.jobs;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException; import devutility.test.app.quartz.common.Executor; @DisallowConcurrentExecution
public class ScheduleJob1 implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Executor.execute("ScheduleJob1", 5000); JobDetail jobDetail = context.getJobDetail();
System.out.println(String.format("key: %s", jobDetail.getKey()));
}
}

这种方式每一个Job都需要定义一个类实现org.quartz.Job接口,形式上要比第一种方式更条理。

3. 我们定义一个创建任意Job的公共方法,来实现Job类的定时执行:

 package devutility.test.app.quartz.services;

 import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import devutility.internal.models.OperationResult; @Service
public class JobServiceImpl implements JobService {
@Autowired
private Scheduler schedulerFactory1; @Autowired
private Scheduler scheduler; @Override
public void start(String name, String group, String cronExpression, Class<? extends Job> clazz) throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(name, group).build(); String triggerName = String.format("trigger_%s", name);
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerName, group).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger); if (!scheduler.isStarted()) {
scheduler.start();
}
}

28行创建一个新的JobDetail,并将Job实现类的Class对象赋值给它;

32行创建了一个新的Trigger;

33-37行使用注入进来的scheduler来运行一个定时任务。

三、对Job的CRUD操作

创建和运行Job上面已经讲过了,下面说一下Job的暂停、中断、删除和更新操作。

1. 暂停

scheduler有一个方法public void pauseJob(JobKey jobKey),该方法通过暂停trigger的触发来实现暂停job的功能,调用该方法之后正在运行的job会持续运行到业务逻辑处理完毕,等下一个触发条件满足时不再开启新的job。

    public OperationResult pause(String group, String name) {
OperationResult result = new OperationResult();
JobKey jobKey = JobKey.jobKey(name, group); try {
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
e.printStackTrace();
result.setErrorMessage(String.format("Pause Job with name = \"%s\" group = \"%s\" failed, system error!", name, group));
} return result;
}

2. 中断

针对需要中断的Job,quartz专门为其定义了一个接口org.quartz.InterruptableJob:

public interface InterruptableJob extends Job {

    /*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Interface.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/ /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a user
* interrupts the <code>Job</code>.
* </p>
*
* @throws UnableToInterruptJobException
* if there is an exception while interrupting the job.
*/
void interrupt()
throws UnableToInterruptJobException;
}

所有需要支持中断的Job都需要实现这个接口:

package devutility.test.app.quartz.jobs;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.InterruptableJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.UnableToInterruptJobException; import devutility.test.app.quartz.common.Executor; @DisallowConcurrentExecution
public class ScheduleJob2 implements InterruptableJob {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Executor.execute(context, 5000);
} @Override
public void interrupt() throws UnableToInterruptJobException {
System.out.println("ScheduleJob2 is interrupting now。。。");
}
}

然后通过调用Scheduler实例的boolean interrupt(JobKey jobKey)方法,查看StdScheduler的源码,我们发现Scheduler的interrupt方法仅仅是调用了InterruptableJob中的interrupt()方法实现,然后设置了一下自己的interrupted属性就完了,并未做任何其他操作。

所以,如果要实现可以中断的Job,我们需要在InterruptableJob实现类中增加中断的逻辑:

package devutility.test.app.quartz.jobs;

import org.quartz.InterruptableJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.UnableToInterruptJobException; import devutility.test.app.quartz.common.Executor; public class ScheduleJob3 implements InterruptableJob {
private boolean interrupted = false; @Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Executor.execute("ScheduleJob3 sub-task 1", 1000); if (interrupted) {
return;
} Executor.execute("ScheduleJob3 sub-task 2", 1000); if (interrupted) {
return;
} Executor.execute("ScheduleJob3 sub-task 3", 1000);
} @Override
public void interrupt() throws UnableToInterruptJobException {
interrupted = true;
}
}

3. 删除

    public OperationResult delete(String jobName, String jobGroup) {
OperationResult result = new OperationResult();
result.append(String.format("Removing Quartz job: %s", jobName)); try {
if (!schedulerFactory1.deleteJob(JobKey.jobKey(jobName, jobGroup))) {
result.setErrorMessage(String.format("Removing job %s failed!", jobName));
}
} catch (SchedulerException e) {
e.printStackTrace();
result.setErrorMessage(String.format("Removing job %s failed with error!", jobName));
} return result;
}

4. 更新

    public OperationResult update(CronTrigger cronTrigger, String cronExpression) {
OperationResult result = new OperationResult();
TriggerKey triggerKey = cronTrigger.getKey();
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger newTrigger = cronTrigger.getTriggerBuilder().withSchedule(scheduleBuilder).build(); try {
schedulerFactory1.rescheduleJob(triggerKey, newTrigger);
result.append(String.format("Update cron trigger %s succeeded!", triggerKey.getName()));
} catch (SchedulerException e) {
e.printStackTrace();
result.setErrorMessage(String.format("Update cron trigger %s failed!", triggerKey.getName()));
} return result;
}

Demo代码

Spring Boot 应用系列 6 -- Spring Boot 2 整合Quartz的更多相关文章

  1. Spring Data JPA系列4——Spring声明式数事务处理与多数据源支持

    大家好,又见面了. 到这里呢,已经是本SpringData JPA系列文档的第四篇了,先来回顾下前面三篇: 在第1篇<Spring Data JPA系列1:JDBC.ORM.JPA.Spring ...

  2. Spring Boot入门系列(六)如何整合Mybatis实现增删改查

    前面介绍了Spring Boot 中的整合Thymeleaf前端html框架,同时也介绍了Thymeleaf 的用法.不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/z ...

  3. Spring Boot入门系列(十八)整合mybatis,使用注解的方式实现增删改查

    之前介绍了Spring Boot 整合mybatis 使用xml配置的方式实现增删改查,还介绍了自定义mapper 实现复杂多表关联查询.虽然目前 mybatis 使用xml 配置的方式 已经极大减轻 ...

  4. Spring Boot 入门系列(二十三)整合Mybatis,实现多数据源配置!

    d之前介绍了Spring Boot 整合mybatis 使用注解方式配置的方式实现增删改查以及一些复杂自定义的sql 语句 .想必大家对spring boot 项目中,如何使用mybatis 有了一定 ...

  5. Spring Boot 应用系列 4 -- Spring Boot 2 整合log4j2

    一.背景 1. log4j2传承于log4j和logback,它是目前性能最好的日志处理工具,有关它们的性能对比请看: 2. 除了性能好之外,log4j2有这么几个重要的新features: (1) ...

  6. Spring Boot 应用系列 3 -- Spring Boot 2 整合MyBatis和Druid,多数据源

    本文演示多数据源(MySQL+SQL Server)的配置,并且我引入了分页插件pagehelper. 1. 项目结构 (1)db.properties存储数据源和连接池配置. (2)两个数据源的ma ...

  7. Spring Boot 应用系列 2 -- Spring Boot 2 整合MyBatis和Druid

    本系列将分别演示单数据源和多数据源的配置和应用,本文先演示单数据源(MySQL)的配置. 1. pom.xml文件配置 需要在dependencies节点添加: <!-- MySQL --> ...

  8. Spring Boot入门系列(十九)整合mybatis,使用注解实现动态Sql、参数传递等常用操作!

    前面介绍了Spring Boot 整合mybatis 使用注解的方式实现数据库操作,介绍了如何自动生成注解版的mapper 和pojo类. 接下来介绍使用mybatis 常用注解以及如何传参数等数据库 ...

  9. Spring Boot 应用系列 5 -- Spring Boot 2 整合logback

    上一篇我们梳理了Spring Boot 2 整合log4j2的配置过程,其中讲到了Spring Boot 2原装适配logback,并且在非异步环境下logback和log4j2的性能差别不大,所以对 ...

随机推荐

  1. 清晰易懂!关于PS入门的超详细笔记!

    给大家分享一篇关于PS入门的超详细笔记!原理讲解清晰明了,虽不是新版本解析,但都是新手学习PS必掌懂的一些知识点,灰常的实用,转走收藏学习! 编辑:千锋UI设计 来源:PS学堂

  2. Spring IOC(二)beanName 别名管理

    Spring IOC(二)beanName 别名管理 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.AliasReg ...

  3. [C#.net]WinForm载入窗体完成后自动执行事件

    一.以下是网络上可搜索到的次序 当 Windows Form 应用程序启动时,会以下列顺序引发主要表单的启动事件:        System.Windows.Forms.Control.Handle ...

  4. python入门之深浅copy

    a1=["a","b","c","aa"] b1=a1 a1[0]=" print(a1,b1) 此时结果为: ...

  5. Selenium实战总结(webwiew下拉框定位)

    基于常见的两种下拉框的展示形式: 1.点击弹出下拉框: 2.鼠标移动弹出下拉框(move_to_element) 实例一[鼠标点击弹出的下拉框]: e.g百度首页的设置--高级搜索--时间: 导包: ...

  6. ubuntu禁用n卡驱动(进系统卡死)

    显卡驱动 该发行版依旧内置了Nouveau 开源驱动,这是导致频繁死机的直接原因.接下来要做的三件事情是: 禁用Nouveau 内核模块 安装Intel HD 530 驱动(二选一) 安装NVIDIA ...

  7. 删除GitHub中的项目

    1.找到要删除的项目 2.点击settings,下拉到底部 3.点击delete this repository,输入你要删除的项目名称

  8. lf-8.4 数据的增删改

    MySQL数据操作: DML 在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 使用INSERT实现数据的插入 UPDATE实现数据的更新 使用DELETE实现数据的删除 ...

  9. 很实用的linux 上的svn安装和svnserver 的重启

    虽然在windows上搭建SVN很简单,但是效能却不高,这当然是和linux相比了.然而在linux上搭建SVN却非常繁琐,所以今天这篇文章就来一步一步教您如何在Centos上搭建SVN 安装 #yu ...

  10. JMeter压力测试及服务器状态监控教程

    转载自:https://blog.csdn.net/cbzcbzcbzcbz/article/details/78023327 前段时间公司需要对服务器进行压力测试,包括登录前的页面和登录后的页面,主 ...