在日常项目研发中,定时任务可谓是必不可少的一环,如果面对任务执行周期固定,业务简单的场景,可直接使用 Spring Boot 内置注解方式实现任务;而如果考虑更为复杂的管理任务信息,在可以通过集成 Quartz 等开源轮子来助力业务研发。
本次主要分享一下 Spring Boot 集成 Quartz 任务框架后,如何实现任务的动态管理,更能够让研发人员专注业务任务的研发,那么就要逐一解决如下疑问。
疑问:是否可以通过 API 动态创建任务呢?
疑问:是否可以通过 API 编辑任务的执行时间呢?
疑问:是否可以通过 API 暂停/恢复任务呢?
疑问:是否可以通过 API 删除任务呢?
疑问:是否可以通过页面完成任务的 CRUD 呢?
考虑到下面的操作是一个大工程,为了方便,重新开启一个 Spring Boot 项目,为了进一步熟练使用 Spring Boot 相关各种 starter,本次选用 MyBatis 作为持久层框架。
1. 核心管理代码
1.1 任务控制器
定义 TaskController,提供用户操作任务的相关 API,例如查询任务列表、添加任务、暂停任务、恢复任务、删除任务。
package com.example.demo.quartz.controller; import com.example.demo.quartz.common.Result; import com.example.demo.quartz.service.TaskInfoService; import com.example.demo.quartz.vo.TaskInfoReq; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 定时任务管理 **/ @RestController @RequestMapping("/task") public class TaskController { @Autowired private TaskInfoService taskInfoService; /**定时器列表*/ @PostMapping("/list") public Result list(@RequestBody TaskInfoReq reqVo) { return taskInfoService.selectTaskListByPage(reqVo); } /**定时器修改*/ @PostMapping("/edit") public Result edit(@RequestBody TaskInfoReq reqVo) { return taskInfoService.updateJob(reqVo); } /**暂停任务*/ @PostMapping("/pause") public Result pause(Integer taskId) { return taskInfoService.pauseJob(taskId); } /**增加任务*/ @PostMapping("/add") public Result add(@RequestBody TaskInfoReq taskInfoReq) { return taskInfoService.addJob(taskInfoReq); } /**恢复任务*/ @PostMapping("/resume") public Result resume(Integer taskId) { return taskInfoService.resumeJob(taskId); } /**删除任务*/ @PostMapping("/del") public Result delete(@RequestBody TaskInfoReq reqVo) { return taskInfoService.delete(reqVo); } }
1.2 任务管理
TaskManager 任务管理器,主要接收业务指令,来完成对 Quartz 容器进行操作。
package com.example.demo.quartz.task; import com.example.demo.quartz.entity.TaskInfo; import com.example.demo.quartz.utils.SpringContextUtils; import com.example.demo.quartz.vo.TaskInfoReq; import org.quartz.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 任务管理 * 1、添加任务 2、更新任务 3、暂停任务 4、恢复任务 **/ @Component public class TaskManager { private static final Logger LOGGER = LoggerFactory.getLogger(TaskManager.class); public static final String JOB_DEFAULT_GROUP_NAME = "JOB_DEFAULT_GROUP_NAME"; public static final String TRIGGER_DEFAULT_GROUP_NAME = "TRIGGER_DEFAULT_GROUP_NAME"; @Autowired private Scheduler scheduler; @Autowired private SpringContextUtils springContextUtils; /** * 添加任务 */ public boolean addJob(TaskInfoReq taskInfoReq) { boolean flag = true; if (!CronExpression.isValidExpression(taskInfoReq.getCron())) { LOGGER.error("定时任务表达式有误:{}", taskInfoReq.getCron()); return false; } try { String className = springContextUtils.getBean(taskInfoReq.getJobName()).getClass().getName(); JobDetail jobDetail = JobBuilder.newJob().withIdentity(new JobKey(taskInfoReq.getJobName(), JOB_DEFAULT_GROUP_NAME)) .ofType((Class<Job>) Class.forName(className)) .build(); Trigger trigger = TriggerBuilder.newTrigger() .forJob(jobDetail) .withSchedule(CronScheduleBuilder.cronSchedule(taskInfoReq.getCron())) .withIdentity(new TriggerKey(taskInfoReq.getJobName(), TRIGGER_DEFAULT_GROUP_NAME)) .build(); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); } catch (Exception e) { LOGGER.error("添加定时任务异常:{}", e.getMessage(), e); flag = false; } return flag; } /** * 更新任务 */ public boolean updateJob(TaskInfo taskInfo) { boolean flag = true; try { JobKey jobKey = new JobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME); TriggerKey triggerKey = new TriggerKey(taskInfo.getJobName(), TRIGGER_DEFAULT_GROUP_NAME); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if (scheduler.checkExists(jobKey) && scheduler.checkExists(triggerKey)) { Trigger newTrigger = TriggerBuilder.newTrigger() .forJob(jobDetail) .withSchedule(CronScheduleBuilder.cronSchedule(taskInfo.getCron())) .withIdentity(triggerKey) .build(); scheduler.rescheduleJob(triggerKey, newTrigger); } else { LOGGER.info("更新任务失败,任务不存在,任务名称:{},表达式:{}", taskInfo.getJobName(), taskInfo.getCron()); } LOGGER.info("更新任务成功,任务名称:{},表达式:{}", taskInfo.getJobName(), taskInfo.getCron()); } catch (SchedulerException e) { LOGGER.error("更新定时任务失败:{}", e.getMessage(), e); flag = false; } return flag; } /** * 暂停任务 */ public boolean pauseJob(TaskInfo taskInfo) { try { scheduler.pauseJob(JobKey.jobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME)); LOGGER.info("任务暂停成功:{}", taskInfo.getId()); return true; } catch (SchedulerException e) { LOGGER.error("暂停定时任务失败:{}", e.getMessage(), e); return false; } } /** * 恢复任务 */ public boolean resumeJob(TaskInfo taskInfo) { try { scheduler.resumeJob(JobKey.jobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME)); LOGGER.info("任务恢复成功:{}", taskInfo.getId()); return true; } catch (SchedulerException e) { LOGGER.error("恢复定时任务失败:{}", e.getMessage(), e); return false; } } }
1.3 启动管理
1.3.1 QuartzManager
Spring Boot 容器启动时,加载启动所有任务。
package com.example.demo.quartz.config; import com.example.demo.quartz.common.EnumTaskEnable; import com.example.demo.quartz.entity.TaskInfo; import com.example.demo.quartz.service.TaskInfoService; import com.example.demo.quartz.vo.TaskInfoReq; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.annotation.PostConstruct; import java.util.List; @Component public class QuartzManager { private Logger logger = LoggerFactory.getLogger(QuartzManager.class); @Autowired private Scheduler scheduler; @Autowired private SpringJobFactory springJobFactory; @Autowired private TaskInfoService taskInfoService; @PostConstruct public void start() { //启动所有任务 try { scheduler.setJobFactory(springJobFactory); // scheduler.clear(); List<TaskInfo> tasks = taskInfoService.selectTasks(); for (TaskInfo taskInfo : tasks) { if (EnumTaskEnable.START.getCode().equals(taskInfo.getStatus()) && !StringUtils.isEmpty(taskInfo.getCron())) { TaskInfoReq data=new TaskInfoReq(); BeanUtils.copyProperties(taskInfo,data); taskInfoService.addJob(data); } } logger.info("定时任务启动完成"); } catch (SchedulerException e) { logger.error(e.getMessage(), e); throw new RuntimeException("定时任务初始化失败"); } } }
1.3.2 SpringJobFactory
package com.example.demo.quartz.config; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.AdaptableJobFactory; import org.springframework.stereotype.Component; /** * 解决spring bean注入Job的问题 */ @Component public class SpringJobFactory extends AdaptableJobFactory { @Autowired private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { // 调用父类的方法 Object jobInstance = super.createJobInstance(bundle); // 进行注入 capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }
2. 支撑代码(表、entity、dao、service、utils)
支撑代码主要完成数据库的 CRUD 操作,实现方式很多种,不局限于 MyBatis,主要是抽象思想:能往数据库插入任务记录、查询任务记录就行。
2.1 任务信息表
CREATE TABLE `SC_TASK_INFO` ( `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `cron` varchar(32) DEFAULT NULL COMMENT '定时执行', `job_name` varchar(256) DEFAULT NULL COMMENT '任务名称', `status` char(1) DEFAULT '0' COMMENT '任务开启状态 0-关闭 2-开启', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT COMMENT='定时任务表';
2.2 实体类
package com.example.demo.quartz.entity; import lombok.Data; import java.io.Serializable; import java.util.Date; @Data public class TaskInfo implements Serializable { private Integer id; private String cron; private String jobName; private String status; private Date createTime; private Date updateTime; }
2.3 TaskInfoDao 定义
package com.example.demo.quartz.dao; import com.example.demo.quartz.entity.TaskInfo; import com.example.demo.quartz.vo.TaskInfoReq; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface TaskInfoDao { TaskInfo selectByJobName(String jobName); List<TaskInfo> selectAll(); List<TaskInfo> selectTaskInfos(TaskInfoReq taskInfo); int deleteByPrimaryKey(Integer id); int insertSelective(TaskInfo record); TaskInfo selectByPrimaryKey(Integer id); int updateByPrimaryKeySelective(TaskInfo record); }
2.4 TaskInfoService 定义
package com.example.demo.quartz.service; import com.example.demo.quartz.common.Result; import com.example.demo.quartz.entity.TaskInfo; import com.example.demo.quartz.vo.TaskInfoReq; import java.util.List; /** * 定时任务接口 **/ public interface TaskInfoService { /**获取任务列表分页*/ Result selectTaskListByPage(TaskInfoReq taskInfoReq); /**添加定时任务*/ Result addJob(TaskInfoReq taskInfoReq); /**更新任务*/ Result updateJob(TaskInfoReq taskInfoReq); /**暂停任务*/ Result pauseJob(Integer taskId); /**恢复任务*/ Result resumeJob(Integer taskId); /**获取所有任务*/ List<TaskInfo> selectTasks(); /**删除任务*/ Result delete(TaskInfoReq reqVo); }
2.5 TaskInfoServiceImpl 业务实现类定义
package com.example.demo.quartz.service.impl; import com.example.demo.quartz.common.CodeMsg; import com.example.demo.quartz.common.EnumTaskEnable; import com.example.demo.quartz.common.ResponseFactory; import com.example.demo.quartz.common.Result; import com.example.demo.quartz.dao.TaskInfoDao; import com.example.demo.quartz.entity.TaskInfo; import com.example.demo.quartz.service.TaskInfoService; import com.example.demo.quartz.task.TaskManager; import com.example.demo.quartz.vo.TaskInfoReq; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.quartz.CronExpression; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.Date; import java.util.List; import java.util.Objects; /** * 定时任务业务实现 **/ @Service public class TaskInfoServiceImpl implements TaskInfoService { private static final Logger LOGGER = LoggerFactory.getLogger(TaskInfoServiceImpl.class); @Resource private TaskInfoDao taskInfoDao; @Resource private TaskManager taskManager; @Override public Result selectTaskListByPage(TaskInfoReq taskInfoReq) { PageHelper.startPage(taskInfoReq.getPageCurrent(), taskInfoReq.getPageSize()); List<TaskInfo> list = taskInfoDao.selectTaskInfos(taskInfoReq); PageInfo<TaskInfo> pageInfo = new PageInfo<>(list); return ResponseFactory.build(pageInfo); } @Override @Transactional(rollbackFor = Exception.class) public Result updateJob(TaskInfoReq taskInfoReq) { if (!CronExpression.isValidExpression(taskInfoReq.getCron())) { LOGGER.error("更新任务失败,表达式有误:{}", taskInfoReq.getCron()); return ResponseFactory.build(CodeMsg.TASK_CRON_ERROR); } TaskInfo isExistData = taskInfoDao.selectByJobName(taskInfoReq.getJobName()); //当任务存在,则更改失败 if ((!Objects.isNull(isExistData)) && (!isExistData.getId().equals(taskInfoReq.getId()))) { return ResponseFactory.build(CodeMsg.TASK_CRON_DOUBLE); } TaskInfo data = taskInfoDao.selectByPrimaryKey(taskInfoReq.getId()); if (data == null) { return ResponseFactory.build(CodeMsg.TASK_NOT_EXITES); } BeanUtils.copyProperties(taskInfoReq, data); data.setUpdateTime(new Date()); taskInfoDao.updateByPrimaryKeySelective(data); if (!taskManager.updateJob(data)) { return ResponseFactory.build(CodeMsg.TASK_EXCEPTION); } return ResponseFactory.build(); } @Override public Result pauseJob(Integer taskId) { TaskInfo data = taskInfoDao.selectByPrimaryKey(taskId); if (data == null) { return ResponseFactory.build(CodeMsg.TASK_NOT_EXITES); } if (!taskManager.pauseJob(data)) { return ResponseFactory.build(CodeMsg.TASK_EXCEPTION); } data.setStatus(EnumTaskEnable.STOP.getCode()); taskInfoDao.updateByPrimaryKeySelective(data); return ResponseFactory.build(); } @Override public Result resumeJob(Integer taskId) { TaskInfo data = taskInfoDao.selectByPrimaryKey(taskId); if (data == null) { return ResponseFactory.build(CodeMsg.TASK_NOT_EXITES); } if (!taskManager.resumeJob(data)) { return ResponseFactory.build(CodeMsg.TASK_EXCEPTION); } data.setStatus(EnumTaskEnable.START.getCode()); taskInfoDao.updateByPrimaryKeySelective(data); return ResponseFactory.build(); } @Override public Result addJob(TaskInfoReq taskInfoReq) { if (!taskManager.addJob(taskInfoReq)) { return ResponseFactory.build(CodeMsg.TASK_EXCEPTION); } TaskInfo data = taskInfoDao.selectByJobName(taskInfoReq.getJobName()); //当任务不存在,则返回成功插入 if (Objects.isNull(data)) { data = new TaskInfo(); BeanUtils.copyProperties(taskInfoReq, data); data.setCreateTime(new Date()); taskInfoDao.insertSelective(data); return ResponseFactory.build(); } else { return ResponseFactory.build(CodeMsg.TASK_CRON_DOUBLE); } } @Override public Result delete(TaskInfoReq reqVo) { try { //TODO 删除任务只是做了暂停,如果是 Quartz Jdbc 模式下添加重复任务可能加不进去,并没有真正删除(可自行调整) Result result = this.pauseJob(reqVo.getId()); //只有暂停成功的任务才能删除 if (CodeMsg.SUCCESS == result.getCode()) { taskInfoDao.deleteByPrimaryKey(reqVo.getId()); return ResponseFactory.build(); } else { return ResponseFactory.build(CodeMsg.TASK_EXCEPTION); } } catch (Exception e) { return ResponseFactory.build(CodeMsg.TASK_EXCEPTION); } } @Override public List<TaskInfo> selectTasks() { return taskInfoDao.selectAll(); } }
2.6 TaskInfoMapper.xml 文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.example.demo.quartz.dao.TaskInfoDao"> <resultMap id="BaseResultMap" type="com.example.demo.quartz.entity.TaskInfo"> <id column="id" property="id" jdbcType="INTEGER"/> <result column="cron" property="cron" jdbcType="VARCHAR"/> <result column="job_name" property="jobName" jdbcType="VARCHAR"/> <result column="status" property="status" jdbcType="CHAR"/> <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/> <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/> </resultMap> <sql id="Base_Column_List"> id, cron, job_name, status, create_time, update_time </sql> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer"> select <include refid="Base_Column_List"/> from sc_task_info where id = #{id,jdbcType=INTEGER} </select> <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from sc_task_info where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="com.example.demo.quartz.entity.TaskInfo"> <selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> insert into sc_task_info (cron, job_name, status, create_time, update_time) values (#{cron,jdbcType=VARCHAR}, #{jobName,jdbcType=VARCHAR}, #{status,jdbcType=CHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}) </insert> <insert id="insertSelective" parameterType="com.example.demo.quartz.entity.TaskInfo"> <selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> insert into sc_task_info <trim prefix="(" suffix=")" suffixOverrides=","> <if test="cron != null"> cron, </if> <if test="jobName != null"> job_name, </if> <if test="status != null"> status, </if> <if test="createTime != null"> create_time, </if> <if test="updateTime != null"> update_time, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="cron != null"> #{cron,jdbcType=VARCHAR}, </if> <if test="jobName != null"> #{jobName,jdbcType=VARCHAR}, </if> <if test="status != null"> #{status,jdbcType=CHAR}, </if> <if test="createTime != null"> #{createTime,jdbcType=TIMESTAMP}, </if> <if test="updateTime != null"> #{updateTime,jdbcType=TIMESTAMP}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="com.example.demo.quartz.entity.TaskInfo"> update sc_task_info <set> <if test="cron != null"> cron = #{cron,jdbcType=VARCHAR}, </if> <if test="jobName != null"> job_name = #{jobName,jdbcType=VARCHAR}, </if> <if test="status != null"> status = #{status,jdbcType=CHAR}, </if> <if test="createTime != null"> create_time = #{createTime,jdbcType=TIMESTAMP}, </if> <if test="updateTime != null"> update_time = #{updateTime,jdbcType=TIMESTAMP}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="com.example.demo.quartz.entity.TaskInfo"> update sc_task_info set cron = #{cron,jdbcType=VARCHAR}, job_name = #{jobName,jdbcType=VARCHAR}, status = #{status,jdbcType=CHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=INTEGER} </update> <select id="selectByJobName" resultMap="BaseResultMap" parameterType="java.lang.String"> select * from sc_task_info where job_name=#{jobName} </select> <select id="selectAll" resultMap="BaseResultMap"> select * from sc_task_info </select> <select id="selectTaskInfos" resultMap="BaseResultMap" parameterType="com.example.demo.quartz.vo.TaskInfoReq"> select * from sc_task_info <where> <if test="searchKey != null and searchKey != ''">job_name like concat('%',concat(trim(#{searchKey}),'%')) </if> <if test="status != null and status != ''">and status=#{status}</if> </where> </select> </mapper>
3. 工具类&辅助代码
3.1 TaskInfoReq 类
package com.example.demo.quartz.vo; import lombok.Data; /** * 任务请求类 **/ @Data public class TaskInfoReq { /**任务编号*/ private Integer id; /**任务时间表达式*/ private String cron; /**任务状态*/ private String status; /**任务名称*/ private String jobName; /**每页显示条数*/ private int pageSize=10; /**当前页数*/ private int pageCurrent=1; }
3.2 Result 类定义
package com.example.demo.quartz.common; import lombok.Data; @Data public class Result { private int code; private String msg; private Object retData; }
3.3 响应工具类封装
package com.example.demo.quartz.common; /** * 响应工具类 */ public class ResponseFactory { private static Result commonBuild(int code, String errmsg) { Result result = new Result(); result.setCode(code); if (errmsg == null || errmsg.trim().length() == 0) { result.setMsg(CodeMsg.getMsg(code)); } else { result.setMsg(errmsg); } return result; } public static Result build(int code) { return commonBuild(code, CodeMsg.getMsg(code)); } public static Result build() { return commonBuild(CodeMsg.SUCCESS, null); } public static Result build(Object data) { Result json = commonBuild(CodeMsg.SUCCESS, null); json.setRetData(data); return json; } }
3.4 任务状态枚举类
package com.example.demo.quartz.common; public enum EnumTaskEnable { START("2", "开启"), STOP("0", "关闭"); private String code; private String msg; EnumTaskEnable(String code, String msg) { this.code = code; this.msg = msg; } public String getCode() { return code; } }
3.5 公共返回码
package com.example.demo.quartz.common; import java.util.HashMap; import java.util.Map; /** * 公共返回码 */ public class CodeMsg { private static final Map<Integer, String> MSG = new HashMap<Integer, String>(); //系统 public static final int SUCCESS = 200; public static final int ERROR = 500; //任务 public static final int TASK_NOT_EXITES = 100001; public static final int TASK_EXCEPTION = 100002; public static final int TASK_CRON_ERROR = 100003; public static final int TASK_CRON_DOUBLE = 100004; static { //系统 MSG.put(SUCCESS, "请求成功."); MSG.put(ERROR, "服务器异常."); //任务 MSG.put(TASK_NOT_EXITES, "定时任务不存在"); MSG.put(TASK_EXCEPTION, "设置定时任务失败"); MSG.put(TASK_CRON_ERROR, "表达式有误"); MSG.put(TASK_CRON_DOUBLE, "定时任务已经存在"); } public static String getMsg(int errcode) { return MSG.get(errcode); } }
3.6 SpringContextUtils 工具类
package com.example.demo.quartz.utils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Component @Lazy(false) public class SpringContextUtils implements ApplicationContextAware { // Spring应用上下文环境 private static ApplicationContext applicationContext; /** * 实现ApplicationContextAware接口的回调方法,设置上下文环境 * * @param applicationContext */ public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtils.applicationContext = applicationContext; } /** * @return ApplicationContext */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 获取对象 * 这里重写了bean方法,起主要作用 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws BeansException */ public static <T> T getBean(String name) { try { return (T) applicationContext.getBean(name); } catch (Exception e) { return null; } } public static <T> T getBean(Class<T> clazz) { try { return applicationContext.getBean(clazz); } catch (Exception e) { return null; } } }
3.7 配置文件
3.7.1 application.properties
server.port=${random.int[10000,19999]} #server.port=15158 ## 将 Quartz 持久化方式修改为 jdbc spring.quartz.job-store-type=jdbc ## 实例名称(默认为quartzScheduler) spring.quartz.properties.org.quartz.scheduler.instanceName=SC_Scheduler ## 实例节点 ID 自动生成 spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO ## 修改存储内容使用的类 spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX ## 数据源信息 spring.quartz.properties.org.quartz.jobStore.dataSource=quartz_jobs spring.quartz.properties.org.quartz.dataSource.quartz_jobs.driver=com.mysql.cj.jdbc.Driver spring.quartz.properties.org.quartz.dataSource.quartz_jobs.URL=jdbc:mysql://127.0.0.1:3306/quartz_jobs?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8 spring.quartz.properties.org.quartz.dataSource.quartz_jobs.user=root spring.quartz.properties.org.quartz.dataSource.quartz_jobs.password=123456 ## 开启集群,多个 Quartz 实例使用同一组数据库表 spring.quartz.properties.org.quartz.jobStore.isClustered=true # MySQL 链接信息 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/quartz_jobs?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ## MyBatis 的配置 # Mapper资源文件存放的路径 mybatis.mapper-locations=classpath*:mapper/*.xml # Dao 接口文件存放的目录 mybatis.type-aliases-package=com.example.demo.quartz.dao # 开启 debug,输出 SQL logging.level.com.example.demo.dao=debug #pagehelper propertis文件分页插件配置 pagehelper.helperDialect=mysql pagehelper.reasonable=true pagehelper.supportMethodsArguments=true pagehelper.params.count=countSql
备注:考虑到部署成本问题,若是单机、内存方式存储任务信息,则只可把 Quartz 相关配置通通去掉。
3.7.2 pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo_job</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo_job</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.4</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
4. 任务代码
定义要执行的业务逻辑任务 DongAoJob 类,这块也就是研发人员重点关注的,后续只需实现 Job 业务就行。
package com.example.demo.quartz.task; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; import org.springframework.stereotype.Component; /** * 定义一个调度器要执行的任务 */ @Component public class DongAoJob extends QuartzJobBean { private static final Log logger = LogFactory.getLog(DongAoJob.class); @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { logger.info("幼年是盼盼,青年是晶晶,中年是冰墩墩,生活见好逐渐发福"); } }
5. 程序入口
package com.example.demo.quartz; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoJobApplication { public static void main(String[] args) { SpringApplication.run(DemoJobApplication.class, args); } }
6. 运行验证
其实挂个简单页面就能轻松完成页面化配置任务,本次用 Postman 方式直接调用任务管理的 API。
6.1 添加任务
此时库任务 Id 为7:
控制台:
6.2 查询任务
6.3 编辑任务
控制台输出:
6.4 暂停任务
http://localhost:15158/task/pause?taskId=7
6.5 恢复任务
http://localhost:15158/task/resume?taskId=7
7. 例行回顾
本文是 Spring Boot 项目集成 Quartz 来实现任务的动态管理,主要是代码,感兴趣的可以自行拿去验证改造并用于实践。