Quartz学习笔记之快速入门

Quartz学习笔记

一、什么是Quartz

什么是Quartz?

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

持久性作业 - 就是保持调度定时的状态;
作业管理 - 对调度作业进行有效的管理;

ps: 大部分公司都会用到定时任务这个功能。
拿火车票购票来说,当你下单后,后台就会插入一条待支付的task(job),一般是30分钟,超过30min后就会执行这个job,去判断你是否支付,未支付就会取消此次订单;当你支付完成之后,后台拿到支付回调后就会再插入一条待消费的task(job),Job触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。

在我们实际的项目中,当Job过多的时候,肯定不能人工去操作,这时候就需要一个任务调度框架,帮我们自动去执行这些程序。那么该如何实现这个功能呢?

(1)首先我们需要定义实现一个定时功能的接口,我们可以称之为Task(或Job),如定时发送邮件的task(Job),重启机器的task(Job),优惠券到期发送短信提醒的task(Job),实现接口如下:
img

(2)有了任务之后,还需要一个能够实现触发任务去执行的触发器,触发器Trigger最基本的功能是指定Job的执行时间,执行间隔,运行次数等。

这里写图片描述

(3)有了Job和Trigger后,怎么样将两者结合起来呢?即怎样指定Trigger去执行指定的Job呢?这时需要一个Schedule,来负责这个功能的实现。

这里写图片描述

上面三个部分就是Quartz的基本组成部分:

  • 调度器:Scheduler
  • 任务:JobDetail
  • 触发器:Trigger,包括SimpleTrigger和CronTrigger

那么明白 Quartz了 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。

img

  1. Job 表示一个工作,要执行的具体内容。类似于TimerTask类。需要实现方法 void execute(JobExecutionContext context)
  2. JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
  3. Trigger 代表一个调度参数的配置,什么时候去调。
  4. Scheduler 代表一个调度容器。类似于Timer类。一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调用。
  5. JobListener: 任务执行的监听器,可以监听到任务执行前、后以及未能成功执行抛出异常。

二、快速开始

SpringBoot集成Quartz依然沿用了Spring的典型方式,使用工厂Bean生成Bean的方式。在Quartz中,需要被调度的任务叫做Job,而负责调度任务则是Scheduler。
我们首先需要配置工厂Bean:JobFactory接口,自定义一个AutowiringSpringBeanJobFactory类继承SpringBeanJobFactory(实现了JobFactory接口)

AutowiringSpringBeanJobFactory工厂类将负责生成实现了Job接口的类的实例对象Bean。

1. 第一种集成 Springboot项目启动就执行

<!-- 导入quartz依赖  -->
<dependency>
 <groupId>org.quartz-scheduler</groupId>
 <artifactId>quartz</artifactId>
 <version>2.2.1</version>
</dependency>
快速入门,2步搞定
1.启动类上面加 @EnableScheduling
2.方法上面加上@Scheduled(cron = "0/3 * * * * ? ")3秒执行一次

cron资料: https://www.cnblogs.com/javahr/p/8318728.html

img

img

然后就可以启动看结果了!!!

2. 第二种动态开启定时任务

1.引入依赖(Springboot项目)
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter</artifactId>
     </dependency>
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <optional>true</optional>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
     </dependency>
     <!-- mysql驱动 -->
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.26</version>
     </dependency>
     <!-- mybatis启动器 -->
     <dependency>
         <groupId>org.mybatis.spring.boot</groupId>
         <artifactId>mybatis-spring-boot-starter</artifactId>
         <version>1.3.2</version>
     </dependency>
     <!-- 集成jdbc -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jdbc</artifactId>
     </dependency>
     <!-- web -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <!-- quartz  -->
      <dependency>
         <groupId>org.quartz-scheduler</groupId>
         <artifactId>quartz</artifactId>
         <version>2.2.1</version>
         <exclusions>
             <exclusion>
                 <artifactId>slf4j-api</artifactId>
                 <groupId>org.slf4j</groupId>
             </exclusion>
         </exclusions>
     </dependency>
2.配置文件可选配置
#是否开启监听器 -> 可以不写,默认为不开启
jobListener:
  switch: true
3.创建一个执行任务的job类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author daifeng
 * @create 2021-11-01 15:35
 * 定时任务具体实现类
 */
public class helloJob implements Job{

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 取出JobDataMap中的数据 
        System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1"));
        System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("trigger1"));
        System.out.println("定时任务执行啦:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}
3.创建JobListener监听器
package com.demo.quartz.config;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
public class SchedulerListener implements JobListener {
    public static final String LISTENER_NAME = "QuartSchedulerListener";

    @Override
    public String getName() {
        return LISTENER_NAME; //must return a name
    }

    //任务被调度前
    @Override
    public void jobToBeExecuted(JobExecutionContext context) {

        String jobName = context.getJobDetail().getKey().toString();
        System.out.println("任务被调度前");
        System.out.println("Job : " + jobName + " is going to start...");

    }

    //任务调度被拒了
    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("任务调度被拒了");
        //可以做一些日志记录原因
    }

    //任务被调度后
    @Override
    public void jobWasExecuted(JobExecutionContext context,
                               JobExecutionException jobException) {
        System.out.println("任务被调度后");

        String jobName = context.getJobDetail().getKey().toString();
        System.out.println("Job : " + jobName + " is finished...");

        if (jobException!=null&&!jobException.getMessage().equals("")) {
            System.out.println("Exception thrown by: " + jobName
                    + " Exception: " + jobException.getMessage());
        }
    }
    
}

4.创建SchedulerConfig配置类
package com.demo.quartz.config;

import lombok.SneakyThrows;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.spi.JobFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import java.io.IOException;
import java.util.Properties;

/**
 * 定时配置(可以配置静态定时任务)
 */
@Configuration
public class SchedulerConfig {

    @Autowired
    private SchedulerListener scheduleListener;

   // 是否开启监听器  默认为false
    @Value("${jobListener.switch:false}")
    private boolean jobListener;


    @Bean
    public SchedulerFactory schedulerFactoryBean(){
        return new StdSchedulerFactory();
    }

    @SneakyThrows
    @Bean
    public Scheduler scheduler(@Qualifier("schedulerFactoryBean") SchedulerFactory schedulerFactoryBean){
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        if(jobListener){
            scheduler.getListenerManager().addJobListener(scheduleListener);
        }
        return scheduler;
    }
}

5.自定义接口请求 执行定时任务
import com.demo.quartz.config.helloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author daifeng
 * @create 2021-11-01 15:44
 */
@RestController
public class JobController {

    // 调度器
    @Autowired
    private Scheduler scheduler;

    // @Scheduled(cron = "0/3 * * * * ? ")
    public void text(){
        //每3秒执行一次
        System.out.println("定时任务执行啦:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }

    /**
     * 以请求的方式去开启定时任务
     * 该任务的执行周期为 : 请求执行的时间起 每秒执行一次 且一直执行
     * @throws SchedulerException
     */
    @PostMapping(value = "testTask1")
    public void testTask1( )throws SchedulerException {

        //可用于传值给job   具体看helloJob类如何取值使用
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("jobDetail1","jobDetail1");
        JobDataMap jobDataMap2 = new JobDataMap();
        jobDataMap2.put("trigger1","这是jobDetail1的trigger");

        // 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
        JobDetail jobDetail = JobBuilder.newJob(helloJob.class).usingJobData(jobDataMap)
                .withIdentity("job1", "group1").build();

        // 2、构建Trigger(触发器)实例,每隔1s执行一次
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                .usingJobData(jobDataMap2) //存值到jobDataMap Job类中能取出
                // 可设置触发时间
                .startNow()//立即生效
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(1)//每隔1s执行一次
                 .repeatForever()).build();//一直执行
        //3、执行
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }

    /**
     * 以请求的方式去开启定时任务
     * 该任务的执行周期为 : 请求执行的时间起 每天早上8点都会执行一次  持续一周 即endAt的时间
     * @throws SchedulerException
     */
    @PostMapping(value = "testTask2")
    public void testTask2( )throws SchedulerException {

        // 获取一周后的日期
        Date endDate = new Date();
        Calendar now = Calendar.getInstance();
        now.add(Calendar.DATE, +7);
        Date endAt = now.getTime();

        //可用于传值给job
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("jobDetail1","jobDetail1");
        JobDataMap jobDataMap2 = new JobDataMap();
        jobDataMap2.put("trigger1","这是jobDetail1的trigger");

        // 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
        JobDetail jobDetail = JobBuilder.newJob(helloJob.class).usingJobData(jobDataMap)
                .withIdentity("job1", "group1").build();
        // 2、构建Trigger实例,每隔1s执行一次
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                .usingJobData(jobDataMap2) //存值到jobDataMap
                .startNow() // 立即触发
                .endAt(endAt) // 表示该任务在一周后自动停止   ps:表示触发器结束触发的时间;
                //使用cron表达式    每天中午8点触发
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 * * ?"))
                .build();

        //3、执行
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }

    /**
     * 以请求的方式去开启定时任务
     * 该任务的执行周期为 : 请求执行的时间的第二天开始 每天早上8点都会执行一次  持续一周即endAt的时间
     * @throws SchedulerException
     */
    @PostMapping(value = "testTask3")
    public void testTask3( )throws SchedulerException {


        Calendar now = Calendar.getInstance();
        now.setTime(new Date());
        // 获取一周后的日期
        now.add(Calendar.DATE, +7);
        Date endAt = now.getTime();

        // 获取一天后的日期
        now.add(Calendar.DATE, +1);
        Date startAt = now.getTime();

        //可用于传值给job
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("jobDetail1","jobDetail1");
        JobDataMap jobDataMap2 = new JobDataMap();
        jobDataMap2.put("trigger1","这是jobDetail1的trigger");

        // 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
        JobDetail jobDetail = JobBuilder.newJob(helloJob.class).usingJobData(jobDataMap)
                .withIdentity("job1", "group1").build();
        // 2、构建Trigger实例,每隔1s执行一次
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                .usingJobData(jobDataMap2) //存值到jobDataMap
                .startAt(startAt) // ps:表示触发器开始触发的时间;
                .endAt(endAt) // 表示该任务在一周后自动停止   ps:表示触发器结束触发的时间;
                //使用cron表达式    每天中午8点触发
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 * * ?"))
                .build();

        //3、执行
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }

    /**
     * 移除定时任务
     * @param jobName  JobDetail的name
     * @param jobGroup JobDetail的分组名
     * @throws SchedulerException
     */
    @RequestMapping("/deleteJob")
    public void deleteJob(String jobName,String jobGroup) throws SchedulerException
    {
        JobKey jobKey=new JobKey(jobName,jobGroup);
        scheduler.deleteJob(jobKey);
    }
    /**
     * 暂停定时任务
     * @param jobName
     * @param jobGroup
     * @throws SchedulerException
     */
    @RequestMapping("/pauseJob")
    public void pauseJob(String jobName,String jobGroup) throws SchedulerException
    {
        JobKey jobKey=new JobKey(jobName,jobGroup);
        scheduler.pauseJob(jobKey);
    }
    /**
     * 恢复定时任务
     * @param jobName
     * @param jobGroup
     * @throws SchedulerException
     */
    @RequestMapping("/resumeJob")
    public void resumeJob(String jobName,String jobGroup) throws SchedulerException
    {
        JobKey triggerKey=new JobKey(jobName,jobGroup);
        scheduler.resumeJob(triggerKey);
    }
    /**
     * 清空所有当前scheduler对象下的定时任务【目前只有全局一个scheduler对象】
     * @throws SchedulerException
     */
    @RequestMapping("/clearAll")
    public void clearAll() throws SchedulerException {
        scheduler.clear();
    }

}

6.自定义工具类
package com.demo.quartz.utils;

import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

@Component
public class SchedulerUtils {

    // 调度器
    @Autowired
    private Scheduler scheduler;

    /**
     * 开始定时任务
     * @param jobName
     * @param jobGroup
     * @throws SchedulerException
     */
    public void startJob(String cron,String jobName,String jobGroup,Class<? extends Job> jobClass) throws SchedulerException
    {
        JobKey jobKey=new JobKey(jobName,jobGroup);
        //判断是否存在相同的计划任务
        if(!scheduler.checkExists(jobKey))
        {
            scheduleJob(cron,scheduler,jobName,jobGroup,jobClass);
        }
    }

    /**
     * 移除定时任务
     * @param jobName
     * @param jobGroup
     * @throws SchedulerException
     */
    public void deleteJob(String jobName,String jobGroup) throws SchedulerException
    {
        JobKey jobKey=new JobKey(jobName,jobGroup);
        scheduler.deleteJob(jobKey);
    }
    /**
     * 暂停定时任务
     * @param jobName
     * @param jobGroup
     * @throws SchedulerException
     */
    public void pauseJob(String jobName,String jobGroup) throws SchedulerException
    {
        JobKey jobKey=new JobKey(jobName,jobGroup);
        scheduler.pauseJob(jobKey);
    }
    /**
     * 恢复定时任务
     * @param jobName
     * @param jobGroup
     * @throws SchedulerException
     */
    public void resumeJob(String jobName,String jobGroup) throws SchedulerException
    {
        JobKey triggerKey=new JobKey(jobName,jobGroup);
        scheduler.resumeJob(triggerKey);
    }
    /**
     * 清空所有当前scheduler对象下的定时任务【目前只有全局一个scheduler对象】
     * @throws SchedulerException
     */
    public void clearAll() throws SchedulerException {
        scheduler.clear();
    }



    /**
     * 可根据需要灵活配置  这里只列举一种,具体如何灵活配置,可看(JobController中的3个testTask接口)
     * 动态创建Job
     * 此处的任务可以配置可以放到properties或者是放到数据库中
     * Trigger:name和group 目前和job的name、group一致,之后可以扩展归类
     * @param scheduler
     * @throws SchedulerException
     */
    private void scheduleJob(String cron,Scheduler scheduler,String jobName,String jobGroup,Class<? extends Job> jobClass) throws SchedulerException{
        /*
         *  此处可以先通过任务名查询数据库,如果数据库中存在该任务,更新任务的配置以及触发器
         *  如果此时数据库中没有查询到该任务,则按照下面的步骤新建一个任务,并配置初始化的参数,并将配置存到数据库中
         */
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build();
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }

}

总结:

  1. 新建一个类实现Job并是实现void execute(JobExecutionContext var1) throws JobExecutionException;

  2. 创建JobDetail,封装Job,它是Scheduler(调度器)真正调度的对象,可以设置Job名称,组等信息,且名称不能重复

  3. 定义触发器Trigger,代表一个调度参数的配置,什么时候去调JobDetail,

  4. 创建就Scheduler,代表一个调度器 类似于Timer类。一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调用。

  5. 最后将jobDetail和 Trigger添加到Scheduler中,并启动

    scheduler.scheduleJob(jobDetail, trigger);
    scheduler.start();

代码已上到gitee: https://gitee.com/Lazy_001/quartz.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值