Springboot 整合 Quartz(定时任务框架)

一、java 定时任务调度的实现方式

1、Timer

特点是:简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务;能实现简单的定时任务,稍微复杂点(或要求高一些)的定时任务却不好实现。

2、ScheduledExecutor

鉴于 Timer 的缺陷,Java 5 推出了基于线程池设计的 ScheduledExecutor;
特点:每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。
虽然用 ScheduledExecutor 和 Calendar 能够实现复杂任务调度,但实现起来还是比较麻烦,对开发还是不够友善。

3、Spring Scheduler

Spring 对任务调度的实现支持,可以指定任务的执行时间,但对任务队列和线程池的管控较弱;一般集成于项目中,小任务很方便。

4、开源工具包 JCronTab

JCronTab 则是一款完全按照 crontab 语法编写的 java 任务调度工具。
特点:

  • 可指定任务的执行时间;
  • 提供完全按照 Unix 的 UNIX-POSIX crontab 的格式来规定时间;
  • 支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;
  • JCronTab 内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人;
  • 设计和部署是高性能并可扩展。
5、开源工具包 Quartz
  • 具有强大的调度功能,很容易与 Spring 集成,形成灵活可配置的调度功能;
  • 调度环境的持久化机制:可以保存并恢复调度现场,即使系统因为故障关闭,任务调度现场的数据并不会丢失;timer 没有这些特点;
  • 灵活的应用方式:可以灵活的定义触发器调度的时间表,并可以对触发器与任务进行关联映射;
  • 分布式与集群能力;
二、什么是 Quartz?

Quartz是 OpenSymphony 开源组织在 Job scheduling 领域又一个开源项目,完全由 Java 开发,可以用来执行定时任务,类似于 java.util.Timer。但是相较于 Timer,Quartz 增加了很多功能:

  • 持久性作业 - 就是保持调度定时的状态;
  • 作业管理 - 对调度作业进行有效的管理;
三、Quartz的相关概念
  • Scheduler:调度器,进行任务调度;quartz的大脑。
  • Job:业务job,亦可称业务组件;定时任务的具体执行业务需要实现此接口,调度器会调用此接口的execute方法完成我们的定时业务。
  • JobDetail:用来定义业务Job的实例,我们可以称之为quartz job,很多时候我们谈到的job指的是JobDetail。
  • Trigger:触发器,用来定义一个指定的Job何时被执行。
  • JobBuilder:Job构建器,用来定义或创建JobDetail的实例;JobDetail限定了只能是Job的实例。
  • TriggerBuilder:触发器构建器,用来定义或创建触发器的实例。
四、springboot整合Quartz
1、pom.xml中引入依赖
    <!--quartz依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
2、application.xml 中添加配置项

spring:
datasource:
druid:
url: jdbc:mysql://127.0.0.1:3306/my_testallowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: admin_123
# 异步初始化策略,可加快启动速度
async-init: true
# 初始化时建立物理连接的个数,同最小连接池数量
initial-size: 5
# 最小连接池数量(按需配置)
min-idle: 5
# 最大连接池数量(按需配置)
max-active: 50
# 获取连接超时, -1表示可一直等待
max-wait: 6000
# 是否缓存preparedStatement,缓存prepared-statements,开启的情况下增加字段可能会报错
pool-prepared-statements: false
# 缓存preparedStatement cache大小
max-open-prepared-statements: 20
# 检测连接是否有效的sql
validation-query: select 1
# 申请连接时执行validationQuery检测连接是否有效
test-on-borrow: false
# 归还连接时执行validationQuery检测连接是否有效
test-on-return: false
# 如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
test-while-idle: true
# 两个含义:1.Destroy线程运行周期 2.testWhileIdle判断依据
time-between-eviction-runs-millis: 60000
# 连接保持空闲而不被驱逐的最小时间:5分钟
min-evictable-idle-time-millis: 300000
# 连接保持空闲而不被驱逐的最大时间: 2天,根据生产mysql配置的wait_time配置=2天
max-evictable-idle-time-millis: 172800000
# 是否keep-alive:
# 即当最小空闲连接空闲了min-evictable-idle-time-millis,执行validationQuery进行keepAlive
keep-alive: true
#打印druid统计信息:每天打印一次统计信息日志,后续根据日志帮助优化连接池配置和SQL(按需配置, -1表示关闭)
time-between-log-stats-millis: 86400000
filter:
# 统计filter,druid默认开启
stat:
enabled: true
# 打印慢SQL(如需)
log-slow-sql: true
# 耗时多久为慢SQL(按需配置)
slow-sql-millis: 3000
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource

quartz:
# 任务存储类型
job-store-type: “jdbc”
# 关闭时等待任务完成
wait-for-jobs-to-complete-on-shutdown: false
# 是否覆盖已有的任务
overwrite-existing-jobs: true
# 是否自动启动计划程序
auto-startup: true
# 延迟启动
startup-delay: 0s
jdbc:
# 数据库架构初始化模式(never:从不进行初始化;always:每次都清空数据库进行初始化;embedded:只初始化内存数据库(默认值))
initialize-schema: “always”
# 相关属性配置
properties:
org:
quartz:
scheduler:
# 调度器实例名称
instanceName: QuartzScheduler
# 分布式节点ID自动生成
instanceId: AUTO
jobStore:
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表前缀
tablePrefix: QRTZ_
# 是否开启集群
isClustered: true
# 数据源别名(自定义)
dataSource: quartz
# 分布式节点有效性检查时间间隔(毫秒)
clusterCheckinInterval: 10000
useProperties: false
# 线程池配置
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true

3、创建Quartz 框架使用的 11 张表

**方法一:**若在配置项中initialize-schema: “always”

项目启动后,在数据库中可以看到自动生成了所有以“qrtz_”开头的表。后面initialize-schema 改成“never” 就行。

**方法二:**在压缩包该路径下找到对应的数据库 SQL 执行脚本,拷贝出来去 Navicat 执行;

4、11张表说明

表名

说明

qrtz_blob_triggers

以Blob 类型存储的触发器

qrtz calendars

存放日历信息,quartz可配置一个日历来指定一个时间范围

qrtz_cron triggers

存放cron类型的触发器

qrtz fired triggers

存放已触发的触发器

qrtz job _details

存放一个jobDetail信息

qrtz job listeners

job监听器

qrtz_locks

存储程序的悲观锁的信息(假如使用了悲观锁)

qrtz_paused trigger_graps

存放暂停掉的触发器

qrtz scheduler state

调度器状态

qrtz simple triggers

简单触发器的信息

qrtz_trigger_listeners

触发器监听器

5、简单的 demo 演示

(1)Service 接口

public interface QuartzService {

/\*\*
 \* 新增
 \*
 \* @param jobName
 \* @param cron
 \* @param jobClassName
 \* @return
 \*/
String addCronJob(String jobName, String cron, String jobClassName);

/\*\*
 \* 停止
 \*
 \* @param jobName
 \* @param jobGroup
 \* @param triggerName
 \* @param triggerGroup
 \* @return
 \*/
String deleteCronJob(String jobName, String jobGroup, String triggerName, String triggerGroup);

/\*\*
 \* 立即执行,不定时
 \*
 \* @param jobName
 \* @param jobClassName
 \* @return
 \*/
String executeImmediately(String jobName, String jobClassName);

}

(2)Service 接口实现类

@Service
@Slf4j
public class QuartzServiceImpl implements QuartzService {

@Autowired
private Scheduler scheduler;

private static final String DEFAULT\_JOB\_GROUP = "default\_job\_group";

private static final String DEFAULT\_TRIGGER\_GROUP = "default\_trigger\_group";

private static final String TRIGGER\_PRE = "Trigger\_";

@Override
public String addCronJob(String jobName, String cron, String jobClassName) {
    try {
        // 当前任务不存在才进行添加
        JobKey jobKey = JobKey.jobKey(jobName, DEFAULT\_JOB\_GROUP);
        if (scheduler.checkExists(jobKey)) {
            log.info("\[添加定时任务\]已存在该作业,jobkey为:{}", jobKey);
            return "已存在该作业";
        }

        // 构建 Job
        JobDetail job = JobBuilder.newJob(getClass(jobClassName).getClass())
                .withIdentity(jobKey).build();

        // cron表达式定时构造器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);

        // 构建 Trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(TriggerKey.triggerKey(TRIGGER\_PRE + jobName, DEFAULT\_TRIGGER\_GROUP))

// .startAt(DateUtil.parseDate(start))
// .endAt(DateUtil.parseDate(end))
.withSchedule(cronScheduleBuilder).build();

        // 启动调度器
        scheduler.scheduleJob(job, trigger);
        scheduler.start();
        return "SUCCESS";
    } catch (Exception e) {
        log.error("\[新增定时任务\]失败,报错:", e);
        return "FAIL";
    }

}

@Override
public String deleteCronJob(String jobName, String jobGroup, String triggerName, String triggerGroup) {
    try {

        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);

        TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroup);

        Trigger trigger = scheduler.getTrigger(triggerKey);

        if (null == trigger) {
            log.info("\[停止定时任务\]根据triggerName:{}和triggerGroup:{}未查询到相应的trigger!");
            return "SUCCESS";
        }
        //暂停触发器
        scheduler.pauseTrigger(triggerKey);
        // 移除触发器
        scheduler.unscheduleJob(triggerKey);
        // 删除任务
        scheduler.deleteJob(jobKey);

        log.info("\[停止定时任务\]jobName:{},jobGroup:{}, triggerName:{}, triggerGroup:{},停止--------------", jobName, jobGroup, triggerName, triggerGroup);

        return "SUCCESS";

    } catch (SchedulerException e) {
        log.error("\[停止定时任务\]失败,报错:", e);
        return "FAIL";
    }
}


public static Job getClass(String className) throws Exception {
    Class<> classTemp = Class.forName(className);
    return (Job) classTemp.newInstance();
}

@Override
public String executeImmediately(String jobName, String jobClassName) {
    try {
        JobKey jobKey = JobKey.jobKey(jobName, DEFAULT\_JOB\_GROUP);
        JobDetail job = JobBuilder.newJob(getClass(jobClassName).getClass())
                .withIdentity(jobKey).build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(TriggerKey.triggerKey(TRIGGER\_PRE + jobName, DEFAULT\_TRIGGER\_GROUP))
                .build();

        // 启动调度器
        scheduler.scheduleJob(job, trigger);
        scheduler.start();
        return "SUCCESS";
    } catch (Exception e) {
        log.error("\[立即执行一次任务,不定时\]失败,报错:", e);
        return "FAIL";
    }
}

}

(3)具体的业务类

@Component
@Slf4j
public class TaskJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info(“=====业务逻辑”);
log.info(“jobName:{}”, jobExecutionContext.getJobDetail().getKey().getName());
log.info(“jobGroup:{}”, jobExecutionContext.getJobDetail().getKey().getGroup());
log.info(“triggerName:{}”, jobExecutionContext.getTrigger().getKey().getName());
log.info(“triggerGroup:{}”, jobExecutionContext.getTrigger().getKey().getGroup());
log.info(“上次触发时间:{}”, DateUtil.formatDateTime(jobExecutionContext.getPreviousFireTime()));
log.info(“本次触发时间:{}”, DateUtil.formatDateTime(jobExecutionContext.getFireTime()));
log.info(“下次触发时间:{}”, DateUtil.formatDateTime(jobExecutionContext.getNextFireTime()));
log.info(“调度时间:{}”, DateUtil.formatDateTime(jobExecutionContext.getScheduledFireTime()));
}
}

(4)入参对象

@Data
public class JobInfo {

private String jobName;

private String cron;

private String jobGroup;

private String triggerName;

private String triggerGroup;

}

(5)暴露接口层

@RestController
@RequestMapping(“/quartz”)
public class QuartzController {

@Autowired
private QuartzService quartzService;

@PostMapping("/createJob")
public String createJob(@RequestBody JobInfo jobInfo) {
    return quartzService.addCronJob(jobInfo.getJobName(), jobInfo.getCron(), "com.example.springbootzy.quartz.config.TaskJob");
}

@PostMapping("/deleteJob")
public String deleteJob(@RequestBody JobInfo jobInfo) {
    return quartzService.deleteCronJob(jobInfo.getJobName(), jobInfo.getJobGroup(), jobInfo.getTriggerName(), jobInfo.getTriggerGroup());
}

@PostMapping("/executeImmediately")
public String executeImmediately(@RequestBody JobInfo jobInfo) {
    return quartzService.executeImmediately(jobInfo.getJobName(), "com.example.springbootzy.quartz.config.TaskJob");
}

}

(6)测试:新增一个“每十秒钟执行一次”的定时任务

(7)测试:删除上述已创建的定时任务

(8)测试:只执行一次

(9)其他

// 任务暂停
scheduler.pauseTrigger(TriggerKey.triggerKey(“Trigger的name”,“Trigger的group”));

// 任务恢复
scheduler.resumeTrigger(TriggerKey.triggerKey(“Trigger的name”,“Trigger的group”));

### 集成Spring BootQuartz调度器框架 为了在Spring Boot项目中集成Quartz以实现定时任务,可以利用Spring提供的便捷类来支持Quartz,并减少应用程序与其他组件之间的耦合度[^1]。 #### 创建Spring Boot应用并引入依赖项 首先,在`pom.xml`文件中加入必要的Maven依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> ``` 这一步骤确保了项目的构建配置包含了运行所需的所有库和工具。 #### 定义Job类 接着定义一个实现了`Job`接口的任务执行逻辑。例如创建名为`MyJob.java`的Java源码如下所示: ```java import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class MyJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("Executing job at " + new java.util.Date()); } } ``` 这段代码展示了如何编写简单的作业处理程序,它会在每次触发时打印当前时间戳至控制台。 #### 设置计划表达式 通过配置文件或编程方式设置具体的调度策略。这里采用XML形式指定调度参数作为例子说明(实际开发推荐使用注解或者YAML/Properties格式)。编辑位于资源目录下的`scheduler-context.xml`: ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- Define the bean of our custom job --> <bean id="myJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="app"/> <property name="targetMethod" value="printMessage"/> </bean> <!-- Configure a simple trigger that fires every five seconds --> <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <property name="jobDetail" ref="myJobDetail"/> <property name="startDelay" value="0"/> <property name="repeatInterval" value="5000"/> </bean> <!-- Register the scheduler factory bean --> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="simpleTrigger"/> </list> </property> </bean> </beans> ``` 上述配置指定了每五秒重复一次的任务触发机制[^2]。 #### 启动应用程序 最后修改入口函数中的启动过程,加载自定义上下文环境变量: ```java package com.example.demo; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { // 加载包含quartz配置的应用上下文 new ClassPathXmlApplicationContext("scheduler-context.xml"); SpringApplication.run(DemoApplication.class, args); } } ``` 当调用此方法后,将会初始化Quartz调度服务实例并且按照预定的时间间隔持续循环执行已注册的工作单元直到进程终止为止[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值