摘要
本文将带您讲解如何一步步实现通过quartz实现添加、修改、删除、停止job,以及优化quartz工具类,支持自动停止逻辑
本文将带您讲解如何一步步实现通过quartz实现添加、修改、删除、停止job,以及优化quartz工具类,支持自动停止逻辑
一个定时任务调度框架,简单易用,功能强大可以使实现定时任务的。
支持集群下定时任务处理
支持任务并发阻塞(上一个任务完成后,才能继续下一个任务)
支持通过API对任务的操作,例如新增任务、修改、启动、暂停、停止(可以在代码中进行调用,而无需修改配置文件再次部署)
支持的数据库种类被较多
在Spring Boot中集成Quartz
使用MySql数据库(程序自动导入,无需人工执行脚本)
使用Spring 自身配置的数据源(不再单独配置qz数据源)
通过代码实现动态化添加、修改、暂停、终止job
JDK版本1.8
Spring Boot 版本:2.3.3.RELEASE
开发工具:eclipse
本章节将从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配置 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 ###################################################
注意:
本文配置使用的是yml文件方式,如果您想使用.properties方式,请将上面的冒号改成等号
本文中使用的quartz没有单独配置数据源,而是使用的您在Spring boot中已经配置的数据源(自动识别,可以看下源码,默认支持C3p0,hikari等几种)
本文中默认表结构初始化方式为initialize-schema: never(共有三种方式:always-每次启动都初始化数据库,never-不初始化表结构,embedded,您可以在首次运行时将此方式设置为always,然后再改成never)
在这个配置中dataSource注释掉了,如果您需要quartz单独配置一套数据源,请放开此部分注释
注意,quartz.yml需要被Spring boot发现(或者直接配置到application.yml中),否则即使配置了也不会起作用,而是使用默认的RAM(内存)去保存定时任务的数据(速度快但是应用重启后会丢失,我在此处就栽跟头了)
到这一步,配置已经完成,接下来就该具体的代码了
该部分java类的结构如下:
common包中的Result.java为返回结果类(controller中使用,如您不想使用,请替换为自己的逻辑)
package com.example.demo.common; import java.util.HashMap; import java.util.Map; /** * @project: 郭鹏飞的博客(wwww.pengfeiguo.com) * @description: 响应结果类 * @version 1.0.0 * @errorcode * 错误码: 错误描述 * @author * <li>2020-09-04 825338623@qq.com Create 1.0 * @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; } }
这个包中的类存的是quartz中的job和trigger的名字常量
package com.example.demo.constant; /** * @project: 郭鹏飞的博客(wwww.pengfeiguo.com) * @description: 常量类 * @version 1.0.0 * @errorcode * 错误码: 错误描述 * @author * <li>2020-09-04 825338623@qq.com Create 1.0 * @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"; }
该包中的实体类为封装添加的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; /** * @project: 郭鹏飞的博客(wwww.pengfeiguo.com) * @description: qzmodel。 * @version 1.0.0 * @errorcode * 错误码: 错误描述 * @author * <li>2020-09-04 825338623@qq.com Create 1.0 * @copyright ©2019-2020 */ @Data public class QuartzJobModule { /** * 触发器开始时间 */ 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) ); } }
这个里面有三个类,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: 郭鹏飞的博客(wwww.pengfeiguo.com) * @description: 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: 郭鹏飞的博客(wwww.pengfeiguo.com) * @description: 日期工具类 * @version 1.0.0 * @errorcode * 错误码: 错误描述 * @author * <li>2020-09-04 825338623@qq.com Create 1.0 * @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; } }
由于篇幅限制,剩余内容请点击链接: https://www.blog-china.cn/blog/liuzaiqingshan/home/39/1607941061037