Quartz定时任务 - Springboot之Quartz

什么是Quartz?

一个定时任务调度框架,简单易用,功能强大可以使实现定时任务的。

优点:

  1. 支持集群下定时任务处理
  2. 支持任务并发阻塞(上一个任务完成后,才能继续下一个任务)
  3. 支持通过API对任务的操作,例如新增任务、修改、启动、暂停、停止(可以在代码中进行调用,而无需修改配置文件再次部署)
  4. 支持的数据库种类被较多

目标

  1. 在Spring Boot中集成Quartz
  2. 使用MySql数据库(程序自动导入,无需人工执行脚本)
  3. 使用Spring 自身配置的数据源(不再单独配置qz数据源)
  4. 通过代码实现动态化添加、修改、暂停、终止job

开发环境

  1. JDK版本1.8
  2. Spring Boot 版本:2.3.3.RELEASE
  3. 开发工具:idea

开发实践

本章节将从Pom依赖配置开始,直到成功运行起该程序为止,为各位朋友提供真实可行的代码实现

pom配置

关于Pom.xml中的配置,有两种方式,第一种使用spring-boot封装的依赖,第二种使用org.quartz的依赖
第一种方式:

<!--引入quartz定时框架-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

这种依赖的好处是你不需要考虑qz的版本号,Spring boot会根据自身的版本来适应不同的quartz版本,但是缺点也很明显,你无法使用其他版本的quartz(不同版本的QZ结构稍有差异),而且在这个封装的依赖中,其实里面也仅仅是指定了org.quartz的依赖,并没有其他的配置或者逻辑。

第二种方式

<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.3.2</version>
</dependency>
<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz-jobs</artifactId>
  <version>2.3.2</version>
</dependency>

这种方式是直接引用的quartz,就是第一种方式中的配置的依赖,但是在这里你就可以自己选择quartz的版本(注意:不同版本的qz结构有差异,Spring boot读取文件时有可能会出现问题。例如在Spring boot 2.3.3.RELEASE中,2.2.1的版本自动生成表结构时会报错--jar包中没有对应的sql脚本,而2.3.2就不存在这个问题)
您可以根据自己的需求来选择哪种依赖方式(以上两种只需要使用其中任何一种即可,无需都使用)。

quartz.yml配置

################### Quartz配置 start ##################################################
server:
  port: 8080
  servlet:
    context-path: /quartz
spring:
  application:
    name: demo
  #连接池配置
  datasource:
    #账号配置
    url: jdbc:mysql://localhost:3306/qz_table?allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    #hikari数据库连接池
    hikari:
      pool-name: Retail_HikariCP
      minimum-idle: 5 #最小空闲连接数量
      idle-timeout: 180000 #空闲连接存活最大时间,默认600000(10分钟)
      maximum-pool-size: 10 #连接池最大连接数,默认是10
      auto-commit: true  #此属性控制从池返回的连接的默认自动提交行为,默认值:true
      max-lifetime: 1800000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      connection-timeout: 30000 #数据库连接超时时间,默认30秒,即30000
      connection-test-query: SELECT 1
  quartz:
          #          dataSource:
          #            default:
          #              driver: com.mysql.jdbc.Driver
          #              URL: jdbc:mysql://localhost:3306/jobconfig?useUnicode=true&characterEncoding=utf8
          #              user: root
          #              password: 12345678
          #              maxConnections: 5
    #相关属性配置
    properties:
      org:
        quartz:
          scheduler:
            instanceName: quartzScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: qrtz_
            isClustered: false
            clusterCheckinInterval: 10000
            useProperties: false
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
    #数据库方式
    job-store-type: JDBC
    #初始化表结构
    jdbc:
      initialize-schema: never
#mybatis配置
mybatis:
  type-aliases-package: com.example.demo.entity
  mapper-locations: classpath:mapper/*.xml
#分页配置, pageHelper是物理分页插件
pagehelper:
  #4.0.0以后版本可以不设置该参数,该示例中是5.1.4
  helper-dialect: mysql
  #启用合理化,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页
  reasonable: true
################### Quartz配置 End ###################################################

注意:

  1. 本文配置使用的是yml文件方式,如果您想使用.properties方式,请将上面的冒号改成等号
  2. 本文中使用的quartz没有单独配置数据源,而是使用的您在Spring boot中已经配置的数据源(自动识别,可以看下源码,默认支持C3p0,hikari等几种)
  3. 本文中默认表结构初始化方式为initialize-schema: never(共有三种方式:always-每次启动都初始化数据库,never-不初始化表结构,embedded,您可以在首次运行时将此方式设置为always,然后再改成never)
  4. 在这个配置中dataSource注释掉了,如果您需要quartz单独配置一套数据源,请放开此部分注释
  5. 注意,quartz.yml需要被Spring boot发现(或者直接配置到application.yml中),否则即使配置了也不会起作用,而是使用默认的RAM(内存)去保存定时任务的数据(速度快但是应用重启后会丢失,我在此处就栽跟头了)

到这一步,配置已经完成,接下来就该具体的代码了

逻辑代码实现

该部分java类的结构如下:

common包

common包中的Result.java为返回结果类(controller中使用,如您不想使用,请替换为自己的逻辑)

package com.example.demo.common;
import java.util.HashMap;
import java.util.Map;

/**
 * @project: 
 * @description: 响应结果类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 * @copyright ©2019-2020
 */
public class Result extends HashMap<String, Object> {

    
    public Result() {
      put("code", 200);
   }

   public static Result error() {
      return error(500, "未知异常,请联系管理员");
   }

   public static Result error(String msg) {
      return error(500, msg);
   }

   public static Result error(int code, String msg) {
      Result r = new Result();
      r.put("code", code);
      r.put("msg", msg);
      return r;
   }

   public static Result ok(Object msg) {
      Result r = new Result();
      r.put("msg", msg);
      return r;
   }


   public static Result ok(Map<String, Object> map) {
      Result r = new Result();
      r.putAll(map);
      return r;
   }

   public static Result ok() {
      return new Result();
   }

   @Override
   public Result put(String key, Object value) {
      super.put(key, value);
      return this;
   }
}

constant包

这个包中的类存的是quartz中的job和trigger的名字常量

package com.example.demo.constant;

/**
 * @project: 
 * @description: 常量类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 * @copyright ©2019-2020
 */
public class GloabalConstant {
    public static final String QZ_JOB_GROUP_NAME = "JOB_GROUP_NAME";
    
    public static final String QZ_TRIGGER_GROUP_NAME = "TRIGGER_GROUP_NAME";
    
}

entity包

该包中的实体类为封装添加的job信息的实体类,大家可以根据自己的需求改成自己想要的字段

package com.example.demo.entity;


import java.util.Date;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import lombok.Data;
import lombok.Getter;

/**
 * @project: 
 * @description: qzmodel。
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 * @copyright ©2019-2020
 */
@Data
public class QuartzJobModule {

    /**
     * 触发器开始时间
     */
   @Getter
    private Date startTime;
    /**
     * 触发器结束时间
     */
    private Date endTime;
    /**
     * job名称
     */
    private String jobName;
    /**
     * job组名
     */
    private String jobGroupName;
    /**
     * 定时器名称
     */
    private String triggerName;
    /**
     * 定时器组名
     */
    private String triggerGroupName;
    /**
     * 执行定时任务的具体操作
     */
    private Class jobClass;
    /**
     * cron表达式
     */
    private String cron;
    /**
     * job的附加信息
     */
    private JobDataMap jobDataMap = new JobDataMap();

    /**
     * 校验
     * @return
     */
    public boolean verify(){
        return !(StringUtils.isEmpty(jobName)
        || StringUtils.isEmpty(jobGroupName)
        || StringUtils.isEmpty(triggerName)
        || StringUtils.isEmpty(triggerGroupName)
        || StringUtils.isEmpty(cron)
//        || CollectionUtils.isEmpty(jobDataMap)
        || ObjectUtils.isEmpty(startTime)
        || ObjectUtils.isEmpty(endTime)
        || !ClassUtils.hasMethod(Job.class, "execute", JobExecutionContext.class)
        );
    }
}

utils包

这个里面有三个类,

CronUtil.java为cron工具类,提供将时间转为cron表达式的工具;

DateUtils.java为日期工具类,提供日期格式化的工具类;

QuartzJobComponent.java是最重要的,提供job的CRUD操作,前两个类为封装QuartzJobModule中的属性提供的,可以自己实现,但是第三个类最好不要修改,可以直接拿来用。
CronUtil.java

package com.example.demo.utils;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;

/**
 * @project:
 * @description: cron生成工具类 :提供将时间转为cron表达式的工具
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020
 */
public class CronUtil {
    
    /**
     * @param batchScheduleModel
     * @return
     * @Desc 转化cron表达式
     */
    public static String convertCronExpression(Date startDate, Date endDate, String[] weeks) {
        StringBuffer sb = new StringBuffer();
        sb.append(convertSeconds()).append(" ").append(convertMinutes(startDate)).append(" ")
            .append(convertHours(startDate, endDate)).append(" ").append(convertDay(startDate, endDate)).append(" ")
            .append(convertMonth(startDate, endDate)).append(" ").append(convertWeek(weeks));
        return sb.toString();
    }
    
    /**
     * 获取定时任务开始时间
     * 
     * @param batchScheduleModel
     * @return
     * @throws ParseException
     */
    public static Date getStartDate(Date startDate) throws ParseException {
        String yyyyMMddS = DateUtils.date2String(startDate, "yyyyMMdd");
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDate);
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                           // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int startMin = startCal.get(Calendar.MINUTE);// 分
        String startTime = "2359";
        if (startHour < Integer.parseInt(startTime.substring(0, 2))) {
            startTime = startHour + "" + startMin;
        }
        return DateUtils.string2Date(yyyyMMddS + startTime + "00", "yyyyMMddHHmmss");
    }
    
    /**
     * 获取定时任务结束时间
     * 
     * @param batchScheduleModel
     * @return
     * @throws ParseException
     */
    public static Date getEndDate(Date endDate) throws ParseException {
        String yyyyMMddE = DateUtils.date2String(endDate, "yyyyMMdd");
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(endDate);
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                           // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int startMin = startCal.get(Calendar.MINUTE);// 分
        String endTime = "0000";
        if (startHour < Integer.parseInt(endTime.substring(0, 2))) {
            endTime = startHour + "" + startMin;
        }
        return DateUtils.string2Date(yyyyMMddE + endTime + "00", "yyyyMMddHHmmss");
    }
    
    /**
     * 判断当前时间是否在规则时间范围内
     * 
     * @param timesEntityList
     * @return
     */
    public static boolean isInRuleTimes(Date startDate, Date endDate) {
        Date date = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int hour = cal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                 // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int minute = cal.get(Calendar.MINUTE);// 分
        
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(date);
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
        // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int startMinute = startCal.get(Calendar.MINUTE);// 分
        
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(date);
        int endHour = endCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                       // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int endMinute = cal.get(Calendar.MINUTE);// 分
        if (startHour < hour && hour < endHour) {
            return true;
        }
        if (startHour == hour && hour == endHour && startMinute < minute && minute < endMinute) {
            return true;
        }
        if (startHour == hour && startMinute < minute) {
            return true;
        }
        if (endHour == hour && minute < endMinute) {
            return true;
        }
        return false;
    }
    
    /**
     * 抽取cron中的hour
     * 
     * @param batchRuleTimeEntityList
     * @return
     */
    public static String convertHours(Date startDay, Date endDay) {
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDay);
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                           // 12小时制,calendar.HOUR_OF_DAY 24小时)
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(endDay);
        int endHour = endCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                       // 12小时制,calendar.HOUR_OF_DAY 24小时)
        StringBuffer sb = new StringBuffer();
        sb.append(startHour).append("-").append(endHour);
        return sb.toString();
    }
    
    /**
     * 抽取cron中的minute
     * 
     * @param batchRuleTimeEntityList
     * @return
     */
    public static String convertMinutes(Date startDay) {
        return "* ";
        /*
         * StringBuffer sb = new StringBuffer();
         * batchRuleTimeEntityList.forEach((b)->{
         * String start = b.getStartTime();
         * String end = b.getEndTime();
         * String minS = start.substring(3,5);
         * String minE = end.substring(3,5);
         * sb.append(minS).append("-").append(minE).append(",");
         * });
         * return sb.deleteCharAt(sb.length()-1).toString();
         */
    }
    
    /**
     * 抽取cron中的seconds
     * 
     * @return
     */
    public static String convertSeconds() {
        return "1";
    }
    
    /**
     * 抽取cron中的day, 在这个项目里直接返回?
     * 
     * @param startDate
     * @param endDate
     * @return
     */
    public static String convertDay(Date startDate, Date endDate) {
        return "?";
        /*
         * String start = DateUtil.formatDate(DateUtil.YYYYMMDD, startDate);
         * String end = DateUtil.formatDate(DateUtil.YYYYMMDD, endDate);
         * String dayS = start.substring(6,8);
         * String dayE = end.substring(6,8);
         * return dayS + "-" + dayE;
         */
    }
    
    /**
     * 抽取cron中的month
     * 
     * @param startDate
     * @param endDate
     * @return
     */
    public static String convertMonth(Date startDate, Date endDate) {
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDate);
        // 获取月份(因为在格里高利历和罗马儒略历一年中第一个月为JANUARY,它为0,最后一个月取决于一年中的月份数,所以这个值初始为0,所以需要加1)
        int startMonth = startCal.get(Calendar.MONTH) + 1;
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(endDate);
        // 获取月份(因为在格里高利历和罗马儒略历一年中第一个月为JANUARY,它为0,最后一个月取决于一年中的月份数,所以这个值初始为0,所以需要加1)
        int endMonth = endCal.get(Calendar.MONTH) + 1;
        return startMonth + "-" + endMonth;
    }
    
    /**
     * 抽取cron中的week
     * 
     * @param dayOfWeeks
     * @return
     */
    public static String convertWeek(String[] dayOfWeeks) {
        StringBuffer sb = new StringBuffer();
        for (String dayOfWeek : dayOfWeeks) {
            sb.append(dayOfWeek).append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }
    
    /**
     * 抽取cron中的week
     * 
     * @param dayOfWeeks
     * @return
     */
    public static String convertWeek(String dayOfWeeks) {
        StringBuffer sb = new StringBuffer();
        String[] split = dayOfWeeks.split(",");
        for (String str : split) {
            sb.append(str).append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }
    
}

DateUtils.java

package com.example.demo.utils;

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

import org.springframework.util.StringUtils;

/**
 * @project:
 * @description: 日期工具类 : 提供日期格式化的工具类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 * @copyright ©2019-2020
 */
public class DateUtils {
    
    /**
     * 模式 :yyyyMMddHHmmss
     */
    private static final String YYYYMMDD_HHMMSS = "yyyyMMddHHmmss";
    /**
     * 模式 :yyyyMMdd
     */
    private static final String YYYYMMDD        = "yyyyMMdd";
    
    /**
     * 方法说明:日期类型按照指定格式转成字符串.
     *
     * @param date
     *            日期
     * @param pattern
     *            日期格式
     * @return
     */
    public static String date2String(Date date, String pattern) {
        if (null == date) {
            date = new Date();
        }
        if (StringUtils.isEmpty(pattern)) {
            pattern = "yyyy-MM-dd HH:mm:ss";
        }
        try {
            return getDateFormat(pattern).format(date);
        }
        catch (Exception e) {
            throw e;
        }
    }
    
    /**
     * 方法说明:获取指定模式pattern的SimpleDateFormat对象.
     *
     * @param pattern
     *            日期格式
     * @return
     */
    private static SimpleDateFormat getDateFormat(String pattern) {
        return new SimpleDateFormat(pattern);
    }
    
    /**
     * 方法说明:获取默认模式"yyyyMMdd"的SimpleDateFormat对象.
     *
     * @return
     */
    private static SimpleDateFormat getDateFormat() {
        return new SimpleDateFormat(YYYYMMDD);
    }
    
    /**
     * 日期转换
     *
     * @param srcStr
     *            日期字符串
     * @param pattern
     *            日期格式
     * @return
     * @throws ParseException
     */
    public static String stringFormat(String srcStr, String pattern) throws ParseException {
        Date date = string2Date(srcStr);
        return date2String(date, pattern);
    }
    
    /**
     * 方法说明:日期类型转成yyyyMMdd格式字符串.
     *
     * @param date
     *            日期
     * @return
     */
    public static String date2String(Date date) {
        return date2String(date, YYYYMMDD);
    }
    
    /**
     * 方法说明:字符串转日期类型.
     *
     * @param date
     *            日期字符串
     * @return
     * @throws ParseException
     * @throws Exception
     */
    public static Date string2Date(String date) throws ParseException {
        if (date.length() != 16) {
            return getDateFormat().parse(date);
        }
        else {
            return getDateFormat(YYYYMMDD_HHMMSS).parse(date);
        }
    }
    
    /**
     * 按照转换规则将日期字符串转换为Date类型的时间
     *
     * @param dateString
     *            要转换的日期字符串
     * @param format
     *            转换的格式,例如:YYYYMMDD
     * @return 转换后的Date类型的日期
     * @throws ParseException
     * @throws BusinessException
     *             异常
     */
    public static Date string2Date(String dateString, String format) throws ParseException {
        SimpleDateFormat sd1 = new SimpleDateFormat(format);
        Date date = sd1.parse(dateString);
        return date;
    }
    
}

QuartzJobComponent.java

package com.example.demo.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.TriggerUtils;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.example.demo.constant.GloabalConstant;
import com.example.demo.entity.QuartzJobModule;

import lombok.extern.slf4j.Slf4j;

/**
 * @project:
 * @description: qz工具类 : 提供job的CRUD操作
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 * @copyright ©2019-2020 。
 */
@Slf4j
@Component
public class QuartzJobComponent {
    
    @Autowired
    private Scheduler scheduler;
    
    /**
     * @Description: 添加一个定时任务
     * @param quartzModel
     */
    public void addJob(QuartzJobModule quartzModel) {
        if (quartzModel.verify()) {
            try {
                JobDetail job = JobBuilder.newJob(quartzModel.getJobClass())
                    .withIdentity(quartzModel.getJobName(), quartzModel.getJobGroupName())
                    .setJobData(quartzModel.getJobDataMap()).build();
                // 表达式调度构建器
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzModel.getCron());
                // 按新的cronExpression表达式构建一个新的trigger
                Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(quartzModel.getTriggerName(), quartzModel.getTriggerGroupName())
                    .startAt(quartzModel.getStartTime()).endAt(quartzModel.getEndTime()).withSchedule(
                        scheduleBuilder)
                    .build();
                scheduler.scheduleJob(job, trigger);
                // 启动
                if (!scheduler.isShutdown()) {
                    scheduler.start();
                }
            }
            catch (SchedulerException e) {
                log.error("Add quartz job error, jobName = {}", quartzModel.getJobName());
            }
            
        }
        else {
            log.error("QuartzModel is invalid!");
        }
    }
    
    /**
     * @Description: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
     * @param jobName
     * @param cron
     */
    public void modifyJobTime(String jobName, String cron, Date startDate, Date endDate) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME);
        
        try {
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                    .startAt(startDate)
                    .endAt(endDate).withSchedule(scheduleBuilder).build();
                // 按新的trigger重新设置job执行
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * @Description:修改任务,(可以修改任务名,任务类,触发时间)
     *                                      原理:移除原来的任务,添加新的任务
     * @param oldJobName
     *            :原任务名
     * @param jobName
     * @param jobclass
     * @param cron
     */
    public void modifyJob(String oldJobName, String jobName, Class jobclass, String cron) {
        /*
         * removeJob(oldJobName);
         * addJob(jobName, jobclass, cron);
         * System.err.println("修改任务"+oldJobName);
         */
        TriggerKey triggerKey = TriggerKey.triggerKey(oldJobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME);
        JobKey jobKey = JobKey.jobKey(oldJobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            Trigger trigger = (Trigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            scheduler.pauseTrigger(triggerKey);// 停止触发器
            scheduler.unscheduleJob(triggerKey);// 移除触发器
            scheduler.deleteJob(jobKey);// 删除任务
            System.err.println("移除任务:" + oldJobName);
            
            JobDetail job = JobBuilder.newJob(jobclass).withIdentity(jobName,
                GloabalConstant.QZ_JOB_GROUP_NAME)
                .build();
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
            // 按新的cronExpression表达式构建一个新的trigger
            Trigger newTrigger = TriggerBuilder.newTrigger().withIdentity(jobName,
                GloabalConstant.QZ_TRIGGER_GROUP_NAME)
                .withSchedule(scheduleBuilder).build();
            
            // 交给scheduler去调度
            scheduler.scheduleJob(job, newTrigger);
            
            // 启动
            if (!scheduler.isShutdown()) {
                scheduler.start();
                System.err.println("添加新任务:" + jobName);
            }
            System.err.println("修改任务【" + oldJobName + "】为:" + jobName);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
        
    }
    
    /**
     * @Description: 修改一个任务的触发时间
     * @param triggerName
     * @param triggerGroupName
     * @param cron
     */
    public void modifyJobTime(String triggerName, String triggerGroupName, String cron) {
        TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
        try {
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                // trigger已存在,则更新相应的定时设置
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
                // 按新的trigger重新设置job执行
                scheduler.resumeTrigger(triggerKey);
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * @Description 移除一个任务(使用默认的任务组名,触发器名,触发器组名)
     * @param jobName
     */
    public void removeJob(String jobName) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME);
        JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            Trigger trigger = (Trigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            scheduler.pauseTrigger(triggerKey);// 停止触发器
            scheduler.unscheduleJob(triggerKey);// 移除触发器
            scheduler.deleteJob(jobKey);// 删除任务
            System.err.println("移除任务:" + jobName);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * @Description: 移除一个任务
     * @param jobName
     * @param jobGroupName
     * @param triggerName
     * @param triggerGroupName
     */
    public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, triggerGroupName);
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            scheduler.pauseTrigger(triggerKey);// 停止触发器
            scheduler.unscheduleJob(triggerKey);// 移除触发器
            scheduler.deleteJob(jobKey);// 删除任务
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * @Description:暂停一个任务(使用默认组名)
     * @param jobName
     */
    public void pauseJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            scheduler.pauseJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @Description:暂停一个任务
     * @param jobName
     * @param jobGroupName
     */
    public void pauseJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            scheduler.pauseJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @Description:恢复一个任务(使用默认组名)
     * @param jobName
     */
    public void resumeJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            scheduler.resumeJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @Description:恢复一个任务
     * @param jobName
     * @param jobGroupName
     */
    public void resumeJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            scheduler.resumeJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @Description:启动所有定时任务
     */
    public void startJobs() {
        try {
            scheduler.start();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * @Description 关闭所有定时任务
     */
    public void shutdownJobs() {
        try {
            if (!scheduler.isShutdown()) {
                scheduler.shutdown();
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * @Description: 立即运行任务,这里的立即运行,只会运行一次,方便测试时用。
     * @param jobName
     */
    public void triggerJob(String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName, GloabalConstant.QZ_JOB_GROUP_NAME);
        try {
            scheduler.triggerJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @Description: 立即运行任务,这里的立即运行,只会运行一次,方便测试时用。
     * @param jobName
     * @param jobGroupName
     */
    public void triggerJob(String jobName, String jobGroupName) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
        try {
            scheduler.triggerJob(jobKey);
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @Description: 获取任务状态
     * @param jobName
     *            触发器名
     */
    public String getTriggerState(String jobName) {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, GloabalConstant.QZ_TRIGGER_GROUP_NAME);
        String name = null;
        try {
            Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
            name = triggerState.name();
        }
        catch (SchedulerException e) {
            e.printStackTrace();
        }
        return name;
    }
    
    /**
     * @Description:获取最近5次执行时间
     * @param cron
     */
    public List<String> getRecentTriggerTime(String cron) {
        List<String> list = new ArrayList<String>();
        try {
            CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
            cronTriggerImpl.setCronExpression(cron);
            List<Date> dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 5);
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
            for (Date date : dates) {
                list.add(dateFormat.format(date));
            }
        }
        catch (ParseException e) {
            log.error("GetRecentTriggerTime error, cron = {}", cron, e);
        }
        return list;
    }
    
}

qzComp包

该包中的类为quartz中job调用时实际业务处理类

package com.example.demo.qzComp;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import lombok.extern.slf4j.Slf4j;

/**
 * @project:
 * @description: quartz job 业务处理类 : quartz中job调用时实际业务处理类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 * @copyright ©2019-2020
 */
@DisallowConcurrentExecution
@Slf4j
public class TaskJobDetail extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>");
        String batchId = context.getJobDetail().getKey().getName();
        log.info("执行的任务id为:[{}]", batchId);
    }
    
}

注意:

  1. 该类为实际业务处理类,定时任务调起时,所有的业务都在此处写(该类不能使用Spring的注解,需要通过上下文来引入需要的bean)
  2. 我们可以看到,在该类中有一行代码:String batchId = context.getJobDetail().getKey().getName();该行代码会获取job的名称,我们可以在添加job的时候,将要处理的数据的唯一标识(比如id)设置为jobName,然后在这个业务处理类中就知道到底要处理哪个数据了。

controller包

该包中模拟的是前端请求添加、暂停、修改、停止、删除job的接口,在这个类中,同样有封装job的代码

package com.example.demo.controller;

import java.util.Calendar;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import com.example.demo.common.Result;
import com.example.demo.constant.GloabalConstant;
import com.example.demo.entity.QuartzJobModule;
import com.example.demo.qzComp.TaskJobDetail;
import com.example.demo.utils.CronUtil;
import com.example.demo.utils.QuartzJobComponent;

/**
 * @project: 
 * @description: quartz controller 。
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 * @copyright ©2019-2020
 */
@RestController
@RequestMapping("/job")
public class JobController {
    private final static Logger LOGGER = LoggerFactory.getLogger(JobController.class);
    
    @Autowired
    private QuartzJobComponent quartzJobComponent;
    
    @PostMapping("/add")
    //@RequestMapping(value = "/add", method = RequestMethod.POST)
    @ResponseBody
    public Result save() {
        LOGGER.info("新增任务");
        try {
            QuartzJobModule quartzJobModule = new QuartzJobModule();
            Calendar cal = Calendar.getInstance();
            cal.set(Calendar.YEAR, 2020);
            cal.set(Calendar.MONTH, 8);
            cal.set(Calendar.DATE, 9);
            cal.set(Calendar.HOUR_OF_DAY, 12);
            cal.set(Calendar.MINUTE, 30);
            cal.set(Calendar.SECOND, 00);
            Date startDate = cal.getTime();// 任务开始日期为2020年9月9日12点30分
            
            Calendar endCal = Calendar.getInstance();
            endCal.set(Calendar.YEAR, 2020);
            endCal.set(Calendar.MONTH, 8);
            endCal.set(Calendar.DATE, 12);
            endCal.set(Calendar.HOUR_OF_DAY, 12);
            endCal.set(Calendar.MINUTE, 30);
            endCal.set(Calendar.SECOND, 00);
            Date endDate = endCal.getTime();// 任务结束日期为2020年9月12日12点30分
            
            quartzJobModule.setStartTime(CronUtil.getStartDate(startDate));
            quartzJobModule.setEndTime(CronUtil.getEndDate(endDate));
            // 注意:在后面的任务中需要通过这个JobName来获取你要处理的数据,因此您可以讲这个设置为你要处理的数据的主键,比如id
            quartzJobModule.setJobName("testJobId");
            quartzJobModule.setTriggerName("tesTriggerNmae");
            quartzJobModule.setJobGroupName(GloabalConstant.QZ_JOB_GROUP_NAME);
            quartzJobModule.setTriggerGroupName(GloabalConstant.QZ_TRIGGER_GROUP_NAME);
            String weeks = "1,2,3,5";// 该处模拟每周1,2,3,5执行任务
            String cronExpression = CronUtil.convertCronExpression(startDate,
                    endDate, weeks.split(","));
            quartzJobModule.setCron(cronExpression);
            quartzJobModule.setJobClass(TaskJobDetail.class);
            quartzJobComponent.addJob(quartzJobModule);
        }
        catch (Exception e) {
            e.printStackTrace();
            return Result.ok();
        }
        return Result.ok();
    }
    
    @PostMapping("/edit")
    @ResponseBody
    public Result edit() {
        LOGGER.info("编辑任务");
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2020);
        cal.set(Calendar.MONTH, 8);
        cal.set(Calendar.DATE, 12);
        cal.set(Calendar.HOUR_OF_DAY, 12);
        cal.set(Calendar.MINUTE, 30);
        cal.set(Calendar.SECOND, 00);
        Date startDate = cal.getTime();// 任务开始日期为2020年9月12日12点30分
        
        Calendar endCal = Calendar.getInstance();
        endCal.set(Calendar.YEAR, 2020);
        endCal.set(Calendar.MONTH, 8);
        endCal.set(Calendar.DATE, 24);
        endCal.set(Calendar.HOUR_OF_DAY, 12);
        endCal.set(Calendar.MINUTE, 30);
        endCal.set(Calendar.SECOND, 00);
        Date endDate = endCal.getTime();// 任务结束日期为2020年9月24日12点30分
        // "testJobId"为add方法添加的job的name
        quartzJobComponent.modifyJobTime("testJobId", "/10 *  * ? * *", startDate, endDate);
        return Result.ok();
    }
    
    @PostMapping("/pause")
    @ResponseBody
    public Result pause(String jobName, String jobGroup) {
        LOGGER.info("停止任务");
        quartzJobComponent.pauseJob("testJobId");
        return Result.ok();
    }
    
    @PostMapping("/resume")
    @ResponseBody
    public Result resume(String jobName, String jobGroup) {
        LOGGER.info("恢复任务");
        quartzJobComponent.removeJob("testJobId");
        return Result.ok();
        
    }
    
    @PostMapping("/remove")
    @ResponseBody
    public Result remove(String jobName, String jobGroup) {
        LOGGER.info("移除任务");
        quartzJobComponent.removeJob("testJobId");
        return Result.ok();
    }
}

注意:

  1. 本示例在添加job时,jobGroupName和triggerGroupName都是用的是上面常量类中的常量,您在实际使用中,必须保证添加时的jobGroupName和triggerGroupName和后面要进行暂停、停止等操作时的值完全一致,否则不会起作用
  2. 我们知道,cron表达式还是有一些鸡肋的,比如无法设置9:50-10:10分的定时任务,因此我们在设置代码时尽量规避这类任务(CronUtil.convertCronExpression的作用就是生成一个cron表达式),如果您认为和您的业务有差距,请换成您自己的逻辑即可
    至此,所有的配置和代码都完成了,我们需要调用以下进行验证是否能够成功动态添加job,并且成功调起job

验证是否成功

使用postman,访问http://localhost:8080/quartz/job/add 接口,此时数据库中的表中将会新增进入数据

注意:

使用谷歌浏览器,访问http://localhost:8080/quartz/job/add 接口,会提示:org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported

原因:因为是在浏览器直接登录的。然后浏览器你输入地址的时候需要在url(如果是get方法需要把要传的东西放到url后面,而post方法的话是需要放到body里,所以当你http://localhost:8080/quartz/job/add 时默认用的时get方法)。所以这就是为什么从post方法到了get方法的原因。但是这里用postman的话,post方法确实可以实现;

file

同时我们稍等几秒,控制台上就会打印出如下内容:

2020-09-10 11:31:46.272  INFO 15308 --- [nio-8080-exec-1] c.example.demo.controller.JobController  : 新增任务
2020-09-10 11:31:46.339  INFO 15308 --- [nio-8080-exec-1] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2020-09-10 11:32:26.086  INFO 15308 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2020-09-10 11:32:26.151  INFO 15308 --- [eduler_Worker-1] com.example.demo.qzComp.TaskJobDetail    : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>
2020-09-10 11:32:26.151  INFO 15308 --- [eduler_Worker-1] com.example.demo.qzComp.TaskJobDetail    : 执行的任务id为:[testJobId]
2020-09-10 11:32:30.010  INFO 15308 --- [eduler_Worker-2] com.example.demo.qzComp.TaskJobDetail    : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>
2020-09-10 11:32:30.011  INFO 15308 --- [eduler_Worker-2] com.example.demo.qzComp.TaskJobDetail    : 执行的任务id为:[testJobId]
2020-09-10 11:32:35.023  INFO 15308 --- [eduler_Worker-3] com.example.demo.qzComp.TaskJobDetail    : begin delwith batch task >>>>>>>>>>>>>>>>>>>>>>>
2020-09-10 11:32:35.023  INFO 15308 --- [eduler_Worker-3] com.example.demo.qzComp.TaskJobDetail    : 执行的任务id为:[testJobId]

说明程序调用成功,并且在任务处理类中成功获取到了jobName,可以继续进行后面的业务处理
其他操作不在验证,大家可以自己调用进行测试,现在把代码放出来:
github :https://github.com/wangmingweikong/spring-boot/tree/master/spring-boot-quartz

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot Quartz定时任务是一种在Spring Boot项目中实现定时任务调度的方法。它可以根据设定的时间间隔或者Cron表达式来执行任务。通过引用中提供的源代码和配置文件,可以直接导入Spring Boot项目并配置好数据库即可使用。 为了实现定时任务,需要在项目中创建一个继承自QuartzJobBean的Job类,并重写executeInternal方法。该方法定义了定时任务的具体逻辑,可以在其中执行需要定时执行的业务操作。 另外,还需要创建一个Quartz定时任务的配置类,其中使用@Configuration注解标记为配置类,配置相关的定时任务信息。在该配置类中,可以通过@Bean注解创建JobDetail和Trigger实例,分别定义任务的具体细节和触发器的配置信息。在这个配置类中,我提供了一个示例,用于演示如何配置一个定时任务。 综上所述,Spring Boot Quartz定时任务是一种灵活可靠的定时任务调度方法,可以通过Spring Boot的特性方便地实现任务定时执行。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [springboot+quartz定时任务实现 纯代码](https://download.csdn.net/download/qq_38971617/12284286)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [SpringBoot整合Quartz实现定时任务](https://blog.csdn.net/qq_29305715/article/details/123517569)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值