SpringBoot集成Quartz实现定时任务的方法

1 需求

在我的前后端分离的实验室管理项目中,有一个功能是学生状态统计。我的设计是按天统计每种状态的比例。为了便于计算,在每天0点,系统需要将学生的状态重置,并插入一条数据作为一天的开始状态。另外,考虑到学生的请假需求,请假的申请往往是提前做好,等系统时间走到实际请假时间的时候,系统要将学生的状态修改为请假。

显然,这两个子需求都可以通过定时任务实现。在网上略做搜索以后,我选择了比较流行的定时任务框架Quartz。

2 Quartz

Quartz是一个定时任务框架,其他介绍网上也很详尽。这里要介绍一下Quartz里的几个非常核心的接口。

2.1 Scheduler接口

Scheduler翻译成调度器,Quartz通过调度器来注册、暂停、删除Trigger和JobDetail。Scheduler还拥有一个SchedulerContext,顾名思义就是上下文,通过SchedulerContext我们可以获取到触发器和任务的一些信息。

2.2 Trigger接口

Trigger可以翻译成触发器,通过cron表达式或是SimpleScheduleBuilder等类,指定任务执行的周期。系统时间走到触发器指定的时间的时候,触发器就会触发任务的执行。

2.3 JobDetail接口

Job接口是真正需要执行的任务。JobDetail接口相当于将Job接口包装了一下,Trigger和Scheduler实际用到的都是JobDetail。

3 SpringBoot官方文档解读

SpringBoot官方写了spring-boot-starter-quartz。使用过SpringBoot的同学都知道这是一个官方提供的启动器,有了这个启动器,集成的操作就会被大大简化。

现在我们来看一看SpingBoot2.2.6官方文档,其中第4.20小节Quartz Scheduler就谈到了Quartz,但很可惜一共只有两页不到的内容,先来看看这么精华的文档里能学到些什么。

Spring Boot offers several conveniences for working with the Quartz scheduler, including the
spring-boot-starter-quartz “Starter”. If Quartz is available, a Scheduler is auto-configured (through the SchedulerFactoryBean abstraction).
Beans of the following types are automatically picked up and associated with the Scheduler:
• JobDetail: defines a particular Job. JobDetail instances can be built with the JobBuilder API.
• Calendar.
• Trigger: defines when a particular job is triggered.

翻译一下:

SpringBoot提供了一些便捷的方法来和Quartz协同工作,这些方法里面包括`spring-boot-starter-quartz`这个启动器。如果Quartz可用,Scheduler会通过SchedulerFactoryBean这个工厂bean自动配置到SpringBoot里。
JobDetail、Calendar、Trigger这些类型的bean会被自动采集并关联到Scheduler上。

Jobs can define setters to inject data map properties. Regular beans can also be injected in a similar manner.

翻译一下:

Job可以定义setter(也就是set方法)来注入配置信息。也可以用同样的方法注入普通的bean。

下面是文档里给的示例代码,我直接完全照着写,拿到的却是null。不知道是不是我的使用方式有误。后来仔细一想,文档的意思应该是在创建Job对象之后,调用set方法将依赖注入进去。但后面我们是通过框架反射生成的Job对象,这样做反而会搞得更加复杂。最后还是决定采用给Job类加@Component注解的方法。

文档的其他篇幅就介绍了一些配置,但是介绍得也不全面,看了帮助也并不是很大。详细的配置可以参考w3school的Quartz配置

4 SpringBoot集成Quartz

4.1 建表

我选择将定时任务的信息保存在数据库中,优点是显而易见的,定时任务不会因为系统的崩溃而丢失。

建表的sql语句在Quartz的github中可以找到,里面有针对每一种常用数据库的sql语句,具体地址是:Quartz数据库建表sql

建表以后,可以看到数据库里多了11张表。我们完全不需要关心每张表的具体作用,在添加删除任务、触发器等的时候,Quartz框架会操作这些表。

4.2 引入依赖

pom.xml里添加依赖。

<!-- quartz 定时任务 -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-quartz</artifactId>
 <version>2.2.6.RELEASE</version>
</dependency>

4.3 配置quartz

application.yml中配置quartz。相关配置的作用已经写在注解上。

# spring的datasource等配置未贴出
spring:
 quartz:
  # 将任务等保存化到数据库
  job-store-type: jdbc
  # 程序结束时会等待quartz相关的内容结束
  wait-for-jobs-to-complete-on-shutdown: true
  # QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
  overwrite-existing-jobs: true
  # 这里居然是个map,搞得智能提示都没有,佛了
  properties:
  org:
   quartz:
   	# scheduler相关
   scheduler:
    # scheduler的实例名
    instanceName: scheduler
    instanceId: AUTO
   # 持久化相关
   jobStore:
    class: org.quartz.impl.jdbcjobstore.JobStoreTX
    driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    # 表示数据库中相关表是QRTZ_开头的
    tablePrefix: QRTZ_
    useProperties: false
   # 线程池相关
   threadPool:
    class: org.quartz.simpl.SimpleThreadPool
    # 线程数
    threadCount: 10
    # 线程优先级
    threadPriority: 5
    threadsInheritContextClassLoaderOfInitializingThread: true

4.4 注册周期性的定时任务

第1节中提到的第一个子需求是在每天0点执行的,是一个周期性的任务,任务内容也是确定的,所以直接在代码里注册JobDetail和Trigger的bean就可以了。当然,这些JobDetail和Trigger也是会被持久化到数据库里。

/**
 * Quartz的相关配置,注册JobDetail和Trigger
 * 注意JobDetail和Trigger是org.quartz包下的,不是spring包下的,不要导入错误
 */
@Configuration
public class QuartzConfig {

 @Bean
 public JobDetail jobDetail() {
  JobDetail jobDetail = JobBuilder.newJob(StartOfDayJob.class)
    .withIdentity("start_of_day", "start_of_day")
    .storeDurably()
    .build();
  return jobDetail;
 }

 @Bean
 public Trigger trigger() {
  Trigger trigger = TriggerBuilder.newTrigger()
    .forJob(jobDetail())
    .withIdentity("start_of_day", "start_of_day")
    .startNow()
    // 每天0点执行
    .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?"))
    .build();
  return trigger;
 }
}

builder类创建了一个JobDetail和一个Trigger并注册成为Spring bean。从第3节中摘录的官方文档中,我们已经知道这些bean会自动关联到调度器上。需要注意的是JobDetail和Trigger需要设置组名和自己的名字,用来作为唯一标识。当然,JobDetail和Trigger的唯一标识可以相同,因为他们是不同的类。

Trigger通过cron表达式指定了任务执行的周期。对cron表达式不熟悉的同学可以百度学习一下。

JobDetail里有一个StartOfDayJob类,这个类就是Job接口的一个实现类,里面定义了任务的具体内容,看一下代码:

@Component
public class StartOfDayJob extends QuartzJobBean {
 private StudentService studentService;

 @Autowired
 public StartOfDayJob(StudentService studentService) {
  this.studentService = studentService;
 }

 @Override
 protected void executeInternal(JobExecutionContext jobExecutionContext)
   throws JobExecutionException {
  // 任务的具体逻辑
 }
}

这里面有一个小问题,上面用builder创建JobDetail时,传入了StartOfDayJob.class,按常理推测,应该是Quartz框架通过反射创建StartOfDayJob对象,再调用executeInternal()执行任务。这样依赖,这个Job是Quartz通过反射创建的,即使加了注解@Component,这个StartOfDayJob对象也不会被注册到ioc容器中,更不可能实现依赖的自动装配。

网上很多博客也是这么介绍的。但是根据我的实际测试,这样写可以完成依赖注入,但我还不知道它的实现原理。

4.5 注册无周期性的定时任务

第1节中提到的第二个子需求是学生请假,显然请假是不定时的,一次性的,而且不具有周期性。

4.5节与4.4节大体相同,但是有两点区别:

  • Job类需要获取到一些数据用于任务的执行;任务执行完成后删除Job和Trigger。
  • 业务逻辑是在老师批准学生的请假申请时,向调度器添加Trigger和JobDetail。

实体类:

public class LeaveApplication {
 @TableId(type = IdType.AUTO)
 private Integer id;
 private Long proposerUsername;
 @JsonFormat( pattern = "yyyy-MM-dd HH:mm",timezone="GMT+8")
 private LocalDateTime startTime;
 @JsonFormat( pattern = "yyyy-MM-dd HH:mm",timezone="GMT+8")
 private LocalDateTime endTime;
 private String reason;
 private String state;
 private String disapprovedReason;
 private Long checkerUsername;
 private LocalDateTime checkTime;

 // 省略getter、setter
}

Service层逻辑,重要的地方已在注释中说明。

@Service
public class LeaveApplicationServiceImpl implements LeaveApplicationService {
 @Autowired
 private Scheduler scheduler;

 // 省略其他方法与其他依赖

 /**
  * 添加job和trigger到scheduler
  */
 private void addJobAndTrigger(LeaveApplication leaveApplication) {
  Long proposerUsername = leaveApplication.getProposerUsername();
  // 创建请假开始Job
  LocalDateTime startTime = leaveApplication.getStartTime();
  JobDetail startJobDetail = JobBuilder.newJob(LeaveStartJob.class)
   	// 指定任务组名和任务名
    .withIdentity(leaveApplication.getStartTime().toString(),
      proposerUsername + "_start")
    // 添加一些参数,执行的时候用
    .usingJobData("username", proposerUsername)
    .usingJobData("time", startTime.toString())
    .build();
  // 创建请假开始任务的触发器
  // 创建cron表达式指定任务执行的时间,由于请假时间是确定的,所以年月日时分秒都是确定的,这也符合任务只执行一次的要求。
  String startCron = String.format("%d %d %d %d %d ? %d",
    startTime.getSecond(),
    startTime.getMinute(),
    startTime.getHour(),
    startTime.getDayOfMonth(),
    startTime.getMonth().getValue(),
    startTime.getYear());
  CronTrigger startCronTrigger = TriggerBuilder.newTrigger()
	   // 指定触发器组名和触发器名
    .withIdentity(leaveApplication.getStartTime().toString(),
      proposerUsername + "_start")
    .withSchedule(CronScheduleBuilder.cronSchedule(startCron))
    .build();

  // 将job和trigger添加到scheduler里
  try {
   scheduler.scheduleJob(startJobDetail, startCronTrigger);
  } catch (SchedulerException e) {
   e.printStackTrace();
   throw new CustomizedException("添加请假任务失败");
  }
 }
}

Job类逻辑,重要的地方已在注释中说明。

@Component
public class LeaveStartJob extends QuartzJobBean {
 private Scheduler scheduler;
 private SystemUserMapperPlus systemUserMapperPlus;

 @Autowired
 public LeaveStartJob(Scheduler scheduler,
       SystemUserMapperPlus systemUserMapperPlus) {
  this.scheduler = scheduler;
  this.systemUserMapperPlus = systemUserMapperPlus;
 }

 @Override
 protected void executeInternal(JobExecutionContext jobExecutionContext)
   throws JobExecutionException {
  Trigger trigger = jobExecutionContext.getTrigger();
  JobDetail jobDetail = jobExecutionContext.getJobDetail();
  JobDataMap jobDataMap = jobDetail.getJobDataMap();
  // 将添加任务的时候存进去的数据拿出来
  long username = jobDataMap.getLongValue("username");
  LocalDateTime time = LocalDateTime.parse(jobDataMap.getString("time"));

  // 编写任务的逻辑

  // 执行之后删除任务
  try {
   // 暂停触发器的计时
   scheduler.pauseTrigger(trigger.getKey());
   // 移除触发器中的任务
   scheduler.unscheduleJob(trigger.getKey());
   // 删除任务
   scheduler.deleteJob(jobDetail.getKey());
  } catch (SchedulerException e) {
   e.printStackTrace();
  }
 }
}

5 总结

上文所述的内容应该可以满足绝大部分定时任务的需求。我在查阅网上的博客之后,发现大部分博客里介绍的Quartz使用还是停留在Spring阶段,配置也都是通过xml,因此我在实现了功能以后,将整个过程总结了一下,留给需要的人以及以后的自己做参考。

总体上来说,Quartz实现定时任务还是非常方便的,与SpringBoot整合之后配置也非常简单,是实现定时任务的不错的选择。

5.2 小坑1

在IDEA2020.1版本里使用SpringBoot与Quartz时,报错找不到org.quartz程序包,但是依赖里面明明有org.quartz,类里的import也没有报错,还可以通过Ctrl+鼠标左键直接跳转到相应的类里。后面我用了IDEA2019.3.4就不再有这个错误。那么就是新版IDEA的BUG了。

到此这篇关于SpringBoot集成Quartz实现定时任务的文章就介绍到这了,更多相关SpringBoot集成Quartz实现定时任务内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot整合Quartz实现动态配置定时任务的方法

    前言 在我们日常的开发中,很多时候,定时任务都不是写死的,而是写到数据库中,从而实现定时任务的动态配置,下面就通过一个简单的示例,来实现这个功能. 一.新建一个springboot工程,并添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency

  • SpringBoot定时任务两种(Spring Schedule 与 Quartz 整合 )实现方法

    前言 最近在项目中使用到定时任务,之前一直都是使用Quartz 来实现,最近看Spring 基础发现其实Spring 提供 Spring Schedule 可以帮助我们实现简单的定时任务功能. 下面说一下两种方式在Spring Boot 项目中的使用. Spring Schedule 实现定时任务 Spring Schedule 实现定时任务有两种方式 1. 使用XML配置定时任务, 2. 使用 @Scheduled 注解. 因为是Spring Boot 项目 可能尽量避免使用XML配置的形式,

  • SpringBoot与Quartz集成实现分布式定时任务集群的代码实例

    Spring Boot与Quartz集成实现分布式定时任务集群 直接贴代码 POM <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs

  • springboot整合quartz实现定时任务示例

    在做项目时有时候会有定时器任务的功能,比如某某时间应该做什么,多少秒应该怎么样之类的. spring支持多种定时任务的实现.我们来介绍下使用spring的定时器和使用quartz定时器 1.我们使用spring-boot作为基础框架,其理念为零配置文件,所有的配置都是基于注解和暴露bean的方式. 2.使用spring的定时器: spring自带支持定时器的任务实现.其可通过简单配置来使用到简单的定时任务. @Component @Configurable @EnableScheduling p

  • Springboot整个Quartz实现动态定时任务的示例代码

    简介 Quartz是一款功能强大的任务调度器,可以实现较为复杂的调度功能,如每月一号执行.每天凌晨执行.每周五执行等等,还支持分布式调度.本文使用Springboot+Mybatis+Quartz实现对定时任务的增.删.改.查.启用.停用等功能.并把定时任务持久化到数据库以及支持集群. Quartz的3个基本要素 Scheduler:调度器.所有的调度都是由它控制. Trigger: 触发器.决定什么时候来执行任务. JobDetail & Job: JobDetail定义的是任务数据,而真正的

  • spring boot整合quartz实现多个定时任务的方法

    最近收到了很多封邮件,都是想知道spring boot整合quartz如何实现多个定时任务的,由于本人生产上并没有使用到多个定时任务,这里给个实现的思路. 1.新建两个定时任务,如下: public class ScheduledJob implements Job{ @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("sched

  • 浅谈SpringBoot集成Quartz动态定时任务

    SpringBoot自带schedule 沿用的springboot少xml配置的优良传统,本身支持表达式等多种定时任务 注意在程序启动的时候加上@EnableScheduling @Scheduled(cron="0/5 * * * * ?") public void job(){ System.out.println("每五秒执行一次"); } 为什么要使用Quartz 多任务情况下,quartz更容易管理,可以实现动态配置 执行时间表达式: 表达式示例: 集成

  • SpringBoot集成Quartz实现定时任务的方法

    1 需求 在我的前后端分离的实验室管理项目中,有一个功能是学生状态统计.我的设计是按天统计每种状态的比例.为了便于计算,在每天0点,系统需要将学生的状态重置,并插入一条数据作为一天的开始状态.另外,考虑到学生的请假需求,请假的申请往往是提前做好,等系统时间走到实际请假时间的时候,系统要将学生的状态修改为请假. 显然,这两个子需求都可以通过定时任务实现.在网上略做搜索以后,我选择了比较流行的定时任务框架Quartz. 2 Quartz Quartz是一个定时任务框架,其他介绍网上也很详尽.这里要介

  • SpringBoot集成quartz实现定时任务详解

    目录 准备知识点 什么是Quartz Quartz的体系结构 什么是Quartz持久化 实现案例 - 单实例方式 实现案例 - 分布式方式 后端实现 前端实现 测试效果 准备知识点 需要了解常用的Quartz框架. 什么是Quartz 来源百度百科, 官网地址:http://www.quartz-scheduler.org/ Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来

  • springBoot项目集成quartz开发定时任务案例及注意事项

    目录 quartz下载.安装 实战案例 常见问题及解决方案 quartz下载.安装 Quartz是完全由java开发的一个开源的任务日程管理系统,任务进度管理器就是一个在预先确定的时间到达时,负责执行(或者通知)一段程序或功能的系统,即定时执行某个任务.Quartz用一个小Java库发布文件(.jar文件),这个库文件包含了所有Quartz核心功能.这些功能的主要接口(API)是Scheduler接口.它提供了简单的操作,例如:将任务纳入日程或者从日程中取消,开始/停止/暂停日程进度. 上图是q

  • springboot集成与使用Sentinel的方法

    前言 在上一篇中,我们初步了解了Sentinel的基本概念,以及其有关限流方面的基础理论,本篇将通过简单的与框架进行整合,看看Sentinel如何在实际项目中进行使用 控制台安装与部署 在实际的小微服务中,使用Sentinel做限流还有另一个强大的利器,就是其提供的dashboard,尽管我们可以通过编写Sentinel提供的一些API限流规则封装一些通用的方法,但是这对于很多初次接触Sentinel的同学来说,学习成本仍然不小,而提供的dashboard可以很方便的通过界面配置的方式达到上一篇

  • SpringBoot集成slf4j日志配置的方法

    目录 前言 1.slf4j概述 2.pom.xml的日志依赖 3.application.yml的日志配置 4.logback.xml配置文件定义 5.logback.xml配置文件解析 5.1 定义日志的存储路径 5.2 定义日志的输出格式 5.3 定义控制台输出 5.4 定义日志相关参数 5.5 定义日志的输出级别 6.测试日志输出 7.小结 前言 日志,作为一个应用系统的重要部分,然而并不会在项目预研或需求设计阶段就作为一个功能模块单独规划设计,但这丝毫不影响它在任何一个系统中的重要的地位

  • springboot集成shiro自定义登陆过滤器方法

    目录 前言 自定义UsernamePasswordAuthenticationFilter 覆盖默认的FormAuthenticationFilter 完整UsernamePasswordAuthenticationFilter代码 前言 在上一篇博客springboot集成shiro权限管理简单实现中,用户在登录的过程中,有以下几个问题: 用户在没有登陆的情况下,访问需要权限的接口,服务器自动跳转到登陆页面,前端无法控制: 用户在登录成功后,服务器自动跳转到成功页,前端无法控制: 用户在登录失

  • SpringBoot整合Quartz实现定时任务详解

    目录 Quartz简介 核心概念 Scheduler JobDetail Job Trigger SpringBoot整合Quartz 准备数据库表 Maven相关依赖 配置文件 quartz配置类 创建任务类 创建监听类 运行结果 Quartz简介 Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中.它提供了巨大的灵活性而不牺牲简单性.你能够用它来为执行一个作业而创建简单的或复杂的调度.它有很多特征,如:数据库支持,集群,插件,EJB

  • SpringBoot中使用Quartz管理定时任务的方法

    定时任务在系统中用到的地方很多,例如每晚凌晨的数据备份,每小时获取第三方平台的 Token 信息等等,之前我们都是在项目中规定这个定时任务什么时候启动,到时间了便会自己启动,那么我们想要停止这个定时任务的时候,就需要去改动代码,还得启停服务器,这是非常不友好的事情 直至遇见 Quartz,利用图形界面可视化管理定时任务,使得我们对定时任务的管理更加方便,快捷 一.Quartz 简介 Quartz是一个开源的作业调度框架,它完全由Java写成,并设计用于J2SE和J2EE应用中.它提供了巨大的灵

随机推荐