Jaca定时任务-02-Quartz
时间:2022-11-14 12:30:01
文章目录
- 一:Quartz定时任务框架
-
- 1:简介
- 特点:作业调度
- 3:作业持久层
- 二:Quartz的使用
-
- 1:导入依赖
- 2.创建自定义任务类,实现Job接口
- 3:quartz调度示例
- 4:@DisallowConcurrentExecution和@PersistJobDataAfterExecution
- 5:quartz核心类说明
- 三:springboot集成Quartz-Quartz持久化-利用springboot数据源
-
- 1.数据库准备
- 2:导入依赖
- 3:配置文件
- 4:配置quartz工具类
- 五、配置定时任务类
- 六、配置任务实体类
- 7:配置controller实现web控制定时任务
- 四:springboot集成Quartz-Quartz持久化-使用quartz自己的数据源
-
- 1.数据库准备
- 2:导入依赖
- 3:配置文件和对应数据源@QuartzDataSource
- 4:配置quartz工具类
- 5:配置定时任务类
- 六、配置任务实体类
- 7:配置controller实现web控制定时任务
- 四:Cron表达式
-
- 1:CronTrigger配置格式:
- 2:通配符说明:
- 3:举例说明
- 4:在线cron表达式生成: [http://qqe2.com/cron/inde](http://qqe2.com/cron/inde)
一:Quartz定时任务框架
1:简介
Quartz是定时任务框架
Quartz是OpenSymphony开源组织在Job scheduling该领域的另一个开源项目完全由Java开发类似于执行定期任务java.util.Timer。但是相较于Timer, Quartz增加了许多功能:
持久性作业 - 保持定期调度;
作业管理 - 有效管理调度作业
特点:作业调度
以火车票购票为例。下单后,后台会插入一张待付款的票。task(job),一般30分钟以上,30分钟以上min这将在之后执行job,判断你是否付款,如果你不付款,订单将被取消;付款完成后,后台会插入一个待消费的回调task(job),Job触发日期是火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。
在我们的实际项目中,当Job太多时候,我们不能手动操作。此时,我们需要一个任务调度框架来帮助我们自动执行这些程序。那么如何实现这个功能呢?
我们可以在给定触发时安排操作,触发器可以设置如下:
一天的某个时间
一周的某一天
一年的某个时间
三、作业持久层
可以将定时任务存在表里,这样我们就可以定时修改,也可以动态修改定时任务,也不会像timer一旦重启,线程池中的定时任务将消失
二:Quartz的使用
1:导入依赖
为了方便演示,我们创建了一个maven的web项目,首先引入依赖性
<dependency> <groupId>org.quartz-schedulergroupId> <artifactId>quartzartifactId> <version>2.3.2version> dependency> <dependency> <groupId>org.quartz-schedulergroupId> <artifactId>quartz-jobsartifactId> <version>2.3.2version>
dependency>
2:创建自定义任务类,实现Job接口
package com.job.quartz;
import org.quartz.*;
import java.util.Date;
/** * @author wkl * @create 2022-07-04 15:51 */
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取context上下文中的JobDetail对象
JobDetail jobDetail = jobExecutionContext.getJobDetail();
//获取JobDetail对象中提前放进去的name属性的值-jobdataMap可以放置多个
String name = jobDetail.getJobDataMap().getString("name");
System.out.println(name);
System.out.println("在这里我可以执行定时任务,比如发短信:"+new Date());
}
}
3:quartz调度示例
package com.job.quartz;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/** * @author wkl * @create 2022-07-04 16:11 */
public class TestScheduler {
public static void main(String[] args) {
try {
//1.定义JobDetail,将HelloJob类添加到JobDetail对象中(添加到Job清单)
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withDescription("Job的描述")
//定义JobDetail的JobDetail 'group1.job1': jobClass: 'com.xr.quartz.HelloJob
.withIdentity("helloJob", "jobGroup1")
//定义JobDetail中的属性(可选),这些属性可以在job任务类中的jobDataMap中获取
.usingJobData("name", "admin")
.usingJobData("age", 18)
.build();
//2.定义Trigger触发器,使用简单触发器,设置name/group
//Date startTime = new Date(System.currentTimeMillis() + 3*1000L); 3秒后启动任务
Trigger trigger = TriggerBuilder.newTrigger()
.withDescription("触发器的描述")
//触发器名,组名(与任务组group1不是一个)
.withIdentity("trigger1", "TriggerGroup1")
//立即启动(默认),如设为某一时间再启动使用:.startAt(statTime)
.startNow()
//.withSchedule(SimpleScheduleBuilder.simpleSchedule() //创建简单触发器,使用SimpleTrigger
//.withIntervalInSeconds(5) //每隔5秒执行
//.withRepeatCount(2)) //执行2次(加上第一次执行的会显示3次)
//.repeatForever()) //一直执行,奔腾到老不停歇
//.build();
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
.build();
//3.创建scheduler调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
//启动调度器
scheduler.start();
// //运行一段时间后关闭
// Thread.sleep(20000);
//
// //4.关闭任务调度
// scheduler.shutdown(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4:@DisallowConcurrentExecution和@PersistJobDataAfterExecution
- @DisallowConcurrentExecution:禁止并发地执行同一个job定义(JobDetail定义的group)的多个实例
主要表现为当调度任务2s执行一次,但是执行这个job就花费了3s,如果不加这个注解,那么系统会多开一个线程来执行,保证2s一次,如果加了这个注解,那么会优先执行完上一个job后,再执行下一个 - @PersistJobDataAfterExecution:持久化obDetailr中的obDataMap(对trigger中的datamap无效),因为sheduler每次执行,都会根据JobDetail创建一个新的Job实例,索引从context中获取的jobDateMap都是属于每一个实例的,加上这个注解,获取的jobDateMap中的参数就是一致的,这样可以共同使用参数,比如count计数
5:quartz的核心类说明
分别是 Scheduler(调度器)Job(任务)和 Trigger (触发器),它们是我们使用 Quartz 的关键。
- (1)、Job:定义需要执行的任务。该类需要实现Job接口,只定义一个方法 execute(JobExecutionContext context),在实现类的 execute 方法中编写所需要定时执行的 Job(任务), JobExecutionContext 类提供了调度应用的一些信息。Job 运行时的信息保存在 JobDataMap 实例中。
- (2)、Trigger:负责设置调度策略。该类是一个接口,描述触发 job 执行的时间触发规则。主要有 SimpleTrigger 和 CronTrigger 这两个子类。当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择;而 CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案:如工作日的10:00点执行异常考勤邮件通知
- (3)、Scheduler:调度器就相当于一个容器,装载着任务和触发器。该类是一个接口,代表一个 Quartz 的独立运行容器, Trigger 和 JobDetail 可以注册到 Scheduler 中, 两者在 Scheduler 中拥有各自的组及名称, 组及名称是 Scheduler 查找定位容器中某一对象的依据, Trigger 的组及名称必须唯一, JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。Scheduler 定义了多个接口方法, 允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。
- (4)、JobDetail:描述 Job 的实现类及其它相关的静态信息,如:Job 名字、描述、关联监听器等信息。Quartz 每次调度 Job 时, 都重新创建一个 Job 实例, 所以它不直接接受一个 Job 的实例,相反它接收一个 Job 实现类,以便运行时通过 newInstance() 的反射机制实例化 Job。
- (5)、一个任务可以被多个触发器调度,但是一个触发器只能调度一个任务;
三:springboot集成Quartz-Quartz持久化-利用springboot数据源
之前我们对quartz的基本使用进行了演示,但是这些job的相关信息都是存储在内存中,对于一个持续定时任务比如执行100次一段逻辑,我们有时希望重新启动程序后应该将剩下的次数执行完,而不是重新执行100次。这时就需要持久化。
当然还有另一个原因,job持久化就意味着可以web管理job!
1:数据库准备
Quartz 存储任务信息有两种方式,使用内存或者使用数据库来存储,这里我们采用 MySQL 数据库存储的方式,首先需要新建 Quartz 的相关表,sql 脚本下载地址:http://www.quartz-scheduler.org/downloads/,也可以从我们下载的jar包的org.quartz.impl.jdbcjobstore下可以看到有很多的sql ,找到名称为 tables_mysql.sql,
创建成功后数据库中多出 11 张表
表代表的意思如下:
各个表字段含义可参考:https://blog.csdn.net/sqlgao22/article/details/100697214
2:导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-quartzartifactId>
<version>2.7.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jdbcartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
<scope>providedscope>
dependency>
Quartz 默认使用 c3p0作为数据库连接池
3:配置文件
默认情况下,Quartz 会加载 classpath 下的 quartz.properties 作为配置文件。如果找不到,则会使用 quartz 框架自己 jar 包下 org/quartz 底下的 quartz.properties 文件
application.yml:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
quartz:
job-store-type: jdbc
jdbc:
# 后续运行就可以改成never-表示不再执行sql重新建表语句
initialize-schema: always
properties:
org:
quartz:
threadPool:
threadCount: 5 #更改quartz线程池
4:配置quartz工具类
当springboot集成quartz后,可以直接在组件中注入scheduler,默认使用数据库数据源,其他配置都会读取配置文件
package com.example.boot_quartz.utils;
import com.example.boot_quartz.dto.QuartzBean;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
/** * @BelongsProject: boot_quartz * @BelongsPackage: com.example.boot_quartz.utils * @Author: wangkanglu * @CreateTime: 2022-07-06 17:25 * @Description: TODO * @Version: 1.0 */
@Component
public class QuartzUtils {
@Autowired
private Scheduler scheduler;
/** * 创建定时任务 定时任务创建之后默认启动状态 * * @param quartzBean 定时任务信息类 * @throws Exception */
public void createScheduleJob(QuartzBean quartzBean, Map<String, String> map) {
try {
//获取到定时任务的执行类 必须是类的绝对路径名称
//定时任务类需要是job类的具体实现 QuartzJobBean是job的抽象类。
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(quartzBean.getJobClass());
// 构建定时任务信息
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(quartzBean.getJobName()).build();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
if(!Objects.isNull(map) && !map.isEmpty()){
map.forEach((key, value) -> jobDataMap.put(key, value));
}
// 设置定时任务执行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
// 构建触发器trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzBean.getJobName()).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (ClassNotFoundException e) {
System.out.println("定时任务类路径出错:请输入类的绝对路径");
} catch (SchedulerException e) {
System.out.println("创建定时任务出错:" + e.getMessage());
}
}
/** * 根据任务名称暂停定时任务 * * @param jobName 定时任务名称 * @throws SchedulerException */
public void pauseScheduleJob(String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
System.out.println("暂停定时任务出错:" + e.getMessage());
}
}
/** * 根据任务名称恢复定时任务 * * @param jobName 定时任务名称 * @throws SchedulerException */
public void resumeScheduleJob(String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.resumeJob(jobKey);
} catch (SchedulerException e) {
System.out.println("启动定时任务出错:" + e.getMessage());
}
}
/** * 根据任务名称立即运行一次定时任务 * * @param scheduler 调度器 * @param jobName 定时任务名称 * @throws SchedulerException */
public void runOnce(Scheduler scheduler, String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.triggerJob(jobKey);
} catch (SchedulerException e) {
System.out.println("运行定时任务出错:" + e.getMessage());
}
}
/** * 更新定时任务 * * @param quartzBean 定时任务信息类 * @throws SchedulerException */
public void updateScheduleJob(QuartzBean quartzBean) {
try {
//获取到对应任务的触发器
TriggerKey triggerKey = TriggerKey.triggerKey(quartzBean.getJobName());
//设置定时任务执行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
//重新构建任务的触发器trigger
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置对应的job
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务出错:" + e.getMessage());
}
}
/** * 根据定时任务名称从调度器当中删除定时任务 * * @param jobName 定时任务名称 * @throws SchedulerException */
public void deleteScheduleJob(String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
System.out.println("删除定时任务出错:" + e.getMessage());
}
}
}
5:配置定时任务类
package com.example.boot_quartz.job; import com.example.boot_quartz.utils.DateUtils; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; import java.util.StringJoiner; /** * @BelongsProject: boot_quartz * @BelongsPackage: com.example.boot_quartz.job * @Author: wangkanglu * @CreateTime: 2022-07-06 15:42 * @Description: TODO 这是spring框架下的job,他集成了spring封装后的job,在这里可以直接使用@autoworid注入其他组件,然后执行方法 * @Version: 1.0 */ public class SpringJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { StringJoiner stringJoiner = new StringJoiner("---"); stringJoiner .add("定时任务执行了") .