目录
quartz概述
springboot使用quartz
导入依赖
数据库脚本
配置类:QuartzConfig
定时任务job工厂:JobFactory
监听类:ScheduleJobInitListener
抽象类:AbstractQuartzJob
具体实现类:QuartzJobExecution
工具类:JobInvokeUtils
工具类:BeansUtils
定时任务管理类:QuartzManager
定时任务service实现类
定时任务service
测试类
执行结果
Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现。该项目于 2009 年被 Terracotta 收购,目前是 Terracotta 旗下的一个项目。读者可以到 http://www.quartz-scheduler.org/站点下载 Quartz 的发布版本及其源代码。
maven
org.quartz-scheduler
quartz
2.2.1
org.springframework
spring-context-support
gradle
compile "org.quartz-scheduler:quartz:2.2.1"
compile "org.springframework:spring-context-support"
DROP TABLE IF EXISTS `task`;
CREATE TABLE `flight_task` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`job_group` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务分组',
`job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务名称',
`remaks` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务描述',
`cron` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务表达式',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态 0.正常 1.暂停',
`bean_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务执行时调用哪个类的方法 包名+类名+方法名',
`is_deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已删除',
`created_by` bigint NULL DEFAULT 1 COMMENT '创建者',
`created_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`updated_by` bigint NULL DEFAULT 1 COMMENT '修改者',
`updated_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '定时任务信息表' ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `task_log`;
CREATE TABLE `flight_task_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`job_id` bigint NULL DEFAULT NULL COMMENT '任务id',
`job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称名称',
`job_group` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务分组',
`bean_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '调用目标字符串',
`message` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '日志信息',
`status` tinyint NULL DEFAULT 0 COMMENT '执行状态 0-正常 1-失败',
`exception_info` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '异常信息',
`start_time` datetime(0) NULL DEFAULT NULL COMMENT '开始时间',
`end_time` datetime(0) NULL DEFAULT NULL COMMENT '结束时间',
`is_deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已删除',
`created_by` bigint NULL DEFAULT 1 COMMENT '创建者',
`created_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`updated_by` bigint NULL DEFAULT 1 COMMENT '修改者',
`updated_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 581 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '定时任务日志表' ROW_FORMAT = Dynamic;
package com.springboot.demo.config;
import org.quartz.Scheduler;
import org.quartz.spi.JobFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import java.io.IOException;
import java.util.Properties;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/3 18:01
* @since 1.0.0
*/
@Configuration
public class QuartzConfig {
private static final Logger logger = LoggerFactory.getLogger(QuartzConfig.class);
private JobFactory jobFactory;
public QuartzConfig(com.springboot.demo.config.JobFactory jobFactory) {
this.jobFactory = jobFactory;
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
try {
factory.setSchedulerName("Scheduler");
factory.setQuartzProperties(quartzProperties());
// 延时启动
factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// 可选,QuartzScheduler
// 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
factory.setJobFactory(jobFactory);
// 设置自动启动,默认为true
factory.setAutoStartup(true);
} catch (Exception ex) {
ex.printStackTrace();
}
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
// quartz参数
Properties prop = new Properties();
// 线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
return prop;
}
@Bean(name = "scheduler")
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
package com.springboot.demo.config;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/6 17:47
* @since 1.0.0
*/
@Component
public class JobFactory extends AdaptableJobFactory {
/**
* AutowireCapableBeanFactory接口是BeanFactory的子类
* 可以连接和填充那些生命周期不被Spring管理的已存在的bean实例
*/
private AutowireCapableBeanFactory factory;
public JobFactory(AutowireCapableBeanFactory factory) {
this.factory = factory;
}
/**
* 创建Job实例
*/
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
// 实例化对象
Object job = super.createJobInstance(bundle);
// 进行注入(Spring管理该Bean)
factory.autowireBean(job);
//返回对象
return job;
}
}
package com.springboot.demo.config;
import com.springboot.demo.service.TaskService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/6 11:36
* @since 1.0.0
*/
@Component
@Order(value = 1)
public class ScheduleJobInitListener implements CommandLineRunner {
@Resource
private TaskService taskService;
@Override
public void run(String... args) throws Exception {
try {
taskService.initSchedule();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
package com.springboot.demo.utils;
import java.util.Date;
import com.springboot.demo.entity.Task;
import com.springboot.demo.entity.TaskLog;
import com.springboot.demo.service.TaskLogService;
import com.springboot.demo.task.TaskConstants;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/7 11:29
* @since 1.0.0
*/
public abstract class AbstractQuartzJob implements Job {
private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
/**
* 线程本地变量
*/
private static ThreadLocal threadLocal = new ThreadLocal<>();
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Task task = (Task)context.getMergedJobDataMap().get(TaskConstants.TASK_PROPERTIES);
try {
before(context, task);
doExecute(context, task);
after(context, task, null);
} catch (Exception ex) {
log.error("定时任务执行异常:{}", ex.getMessage());
after(context, task, ex);
}
}
/**
* 执行前
*
* @param context 工作执行上下文对象
* @param task 任务
*/
protected void before(JobExecutionContext context, Task task) {
threadLocal.set(new Date());
}
protected void after(JobExecutionContext context, Task task, Exception ex) {
Date startTime = threadLocal.get();
threadLocal.remove();
TaskLog taskLog = new TaskLog();
taskLog.setJobId(task.getId());
taskLog.setJobGroup(task.getJobGroup());
taskLog.setJobName(task.getJobName());
taskLog.setInvokeTarget(task.getBeanClass());
taskLog.setStartTime(startTime);
taskLog.setEndTime(new Date());
long runMs = taskLog.getEndTime().getTime() - taskLog.getStartTime().getTime();
taskLog.setMessage(taskLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
if (null != ex) {
taskLog.setStatus(1);
taskLog.setExceptionInfo(ex.getMessage());
} else {
taskLog.setStatus(0);
}
BeansUtils.getBean(TaskLogService.class).save(taskLog);
}
/**
* 执行方法
*
* @param context 工作执行上下文对象
* @param task 系统计划任务
* @throws Exception 执行过程中的异常
*/
protected abstract void doExecute(JobExecutionContext context, Task task) throws Exception;
}
package com.springboot.demo.utils;
import com.springboot.demo.entity.Task;
import org.quartz.JobExecutionContext;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/7 18:37
* @since 1.0.0
*/
public class QuartzJobExecution extends AbstractQuartzJob {
@Override
protected void doExecute(JobExecutionContext context, Task task) throws Exception {
JobInvokeUtils.invokeMethod(task);
}
}
package com.springboot.demo.utils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.springboot.demo.entity.Task;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/7 18:03
* @since 1.0.0
*/
public class JobInvokeUtils {
public static void invokeMethod(Task task) throws Exception {
String beanClass = task.getBeanClass();
if (beanClass != null && beanClass.length() != 0) {
String beanName = BeansUtils.getBeanName(beanClass);
String methodName = BeansUtils.getMethodName(beanClass);
Object bean = Class.forName(beanName).newInstance();
invokeMethod(bean, methodName);
}
}
public static void invokeMethod(Object bean, String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = bean.getClass().getDeclaredMethod(methodName);
method.invoke(bean);
}
}
package com.springboot.demo.utils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/8 11:22
* @since 1.0.0
*/
@Component
public final class BeansUtils implements BeanFactoryPostProcessor {
private static ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeansUtils.beanFactory = beanFactory;
}
public static T getBean(Class cla) throws BeansException {
return beanFactory.getBean(cla);
}
/**
* 获取bean名称.
*
* @param beanClass 目标字符串
* @return bean名称
*/
public static String getBeanName(String beanClass) {
int index = beanClass.lastIndexOf(".");
return index == -1 ? beanClass : beanClass.substring(0, index);
}
/**
* 获取bean方法.
*
* @param beanClass 目标字符串
* @return method方法
*/
public static String getMethodName(String beanClass) {
int endIndex = beanClass.indexOf("(");
int startIndex = beanClass.lastIndexOf(".");
if (startIndex == -1) {
return null;
}
return beanClass.substring(startIndex + 1, endIndex);
}
}
package com.springboot.demo.manager;
import com.springboot.demo.entity.Task;
import com.springboot.demo.task.TaskConstants;
import com.springboot.demo.utils.QuartzJobExecution;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.DateBuilder;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
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 javax.annotation.Resource;
import org.springframework.stereotype.Service;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/6 11:49
* @since 1.0.0
*/
@Service
public class QuartzManager {
@Resource
private Scheduler scheduler;
/**
* 得到quartz任务类
*
* @param task 执行计划
* @return 具体执行任务类
*/
private static Class extends Job> getQuartzJobClass(Task task) {
return QuartzJobExecution.class;
}
/**
* 添加任务job
*
* @param task :任务实体类
* @version 1.0.0 2021/9/6 12:10
* @since 1.0.0
*/
@SuppressWarnings("unchecked")
public void addJob(Task task) {
try {
// 创建jobDetail实例, 绑定Job实现类
// 指明job的名称,所在组的名称,以及绑定Job类
Class extends Job> jobClass = getQuartzJobClass(task);
// Class extends Job> jobClass = (Class extends Job>) (Class.forName(task.getBeanClass())).newInstance().getClass();
// 配置任务名称和组构成任务key
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(task.getId(), task.getJobGroup())).build();
// 定义调度规则
// 使用cornTrigger规则
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(task.getJobName(), task.getJobGroup())
.startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND))
.withSchedule(CronScheduleBuilder.cronSchedule(task.getCron())).startNow().build();
jobDetail.getJobDataMap().put(TaskConstants.TASK_PROPERTIES, task);
// 判断是否存在
if (scheduler.checkExists(getJobKey(task.getId(), task.getJobGroup()))) {
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(task.getId(), task.getJobGroup()));
}
// 把job和触发器注册到任务调度中
scheduler.scheduleJob(jobDetail, trigger);
if (task.getStatus() == 1) {
pauseJob(task);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 构建任务键对象
*/
public static JobKey getJobKey(Long jobId, String jobGroup) {
return JobKey.jobKey(TaskConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
/**
* 暂停一个job
*
* @param task :任务实体类
* @version 1.0.0 2021/9/6 12:10
* @since 1.0.0
*/
public void pauseJob(Task task) throws SchedulerException {
scheduler.pauseJob(getJobKey(task.getId(), task.getJobGroup()));
}
/**
* 恢复一个job
*
* @param task :任务实体类
* @version 1.0.0 2021/9/6 12:10
* @since 1.0.0
*/
public void resumeJob(Task task) throws SchedulerException {
scheduler.resumeJob(getJobKey(task.getId(), task.getJobGroup()));
}
/**
* 删除一个job
*
* @param task :任务实体类
* @version 1.0.0 2021/9/6 12:10
* @since 1.0.0
*/
public void deleteJob(Task task) throws SchedulerException {
scheduler.deleteJob(getJobKey(task.getId(), task.getJobGroup()));
}
/**
* 立即触发job
*
* @param task :任务实体类
* @version 1.0.0 2021/9/6 12:10
* @since 1.0.0
*/
public void runJobNow(Task task) throws SchedulerException {
JobDataMap dataMap = new JobDataMap();
dataMap.put(TaskConstants.TASK_PROPERTIES, task);
scheduler.triggerJob(getJobKey(task.getId(), task.getJobGroup()), dataMap);
}
/**
* 更新job表达式
*
* @param task :任务实体类
* @version 1.0.0 2021/9/6 12:10
* @since 1.0.0
*/
public void updateJobCron(Task task) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(task.getJobName(), task.getJobGroup());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCron());
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
scheduler.rescheduleJob(triggerKey, trigger);
}
}
package com.springboot.demo.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.springboot.demo.manager.QuartzManager;
import com.springboot.demo.entity.Task;
import com.springboot.demo.mapper.TaskMapper;
import com.springboot.demo.service.TaskService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import java.util.List;
/**
* (Task)表服务实现类
*
* @since 2021-09-03 17:55:57
*/
@Service
public class TaskServiceImpl extends ServiceImpl implements TaskService {
private final static Logger logger = LoggerFactory.getLogger(TaskServiceImpl.class);
@Resource
private TaskMapper taskMapper;
@Resource
private QuartzManager quartzManager;
@Override
public IPage getListByPage(Page page, Task query) {
return taskMapper.getListByPage(page, query);
}
@Override
public void initSchedule() {
List list = this.list();
for (Task task : list) {
quartzManager.addJob(task);
}
}
@Override
public Boolean updateTaskById(Task task) {
Task oldTask = this.getById(task.getId());
if (this.updateById(task)) {
try {
quartzManager.deleteJob(oldTask);
quartzManager.addJob(task);
return true;
} catch (SchedulerException e) {
logger.error("更新job任务表达式异常:{}", e.getMessage());
return false;
}
}
return false;
}
@Override
public Boolean deleteById(Long id) {
Task task = this.getById(id);
if (task.getStatus() == 0) {
try {
quartzManager.deleteJob(task);
} catch (SchedulerException e) {
logger.error("删除任务异常:{}", e.getMessage());
return false;
}
}
return this.removeById(id);
}
@Override
public Boolean performOneById(Long id) {
Task task = this.getById(id);
try {
quartzManager.runJobNow(task);
return true;
} catch (SchedulerException e) {
logger.error("立即执行任务异常:{}", e.getMessage());
return false;
}
}
@Override
public Boolean performOrSuspendOneById(Long id, Integer status) {
Task task = this.getById(id);
task.setStatus(status);
try {
if (status == 1) {
quartzManager.pauseJob(task);
}
if (status == 0) {
quartzManager.resumeJob(task);
}
} catch (SchedulerException ex) {
logger.error("修改任务状态异常:{}", ex.getMessage());
return false;
}
return this.updateById(task);
}
@Override
public Boolean saveTask(Task task) {
if (this.save(task)) {
quartzManager.addJob(task);
return true;
}
return false;
}
}
package com.springboot.demo.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.springboot.demo.entity.Task;
/**
* (Task)表服务接口
*
* @since 2021-09-03 17:55:56
*/
public interface TaskService extends IService {
/**
* 分页查询.
*
* @param page :
* @param query :
* @return com.baomidou.mybatisplus.core.metadata.IPage
* @since 1.0.0
*/
IPage getListByPage(Page page, Task query);
/**
* 初始化加载任务.
*
* @since 1.0.0
*/
void initSchedule();
/**
* 修改一个任务.
*
* @param task :任务信息
* @return boolean
* @since 1.0.0
*/
Boolean updateTaskById(Task task);
/**
* 根据id删除一个任务.
*
* @param id :
* @return boolean
* @since 1.0.0
*/
Boolean deleteById(Long id);
/**
* 根据id执行一次任务.
*
* @param id :id
* @return boolean
* @since 1.0.0
*/
Boolean performOneById(Long id);
/**
* 根据id和状态判断执行或暂停一个任务.
*
* @param id : id
* @param status :状态 0为执行 1为暂停
* @return boolean
* @since 1.0.0
*/
Boolean performOrSuspendOneById(Long id, Integer status);
/**
* 保存定时任务.
* @param task :
* @return java.lang.Boolean
* @since 1.0.0
*/
Boolean saveTask(Task task);
}
package com.springboot.demo.test;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* Copyright: Copyright (c) 2021
*
* Description:
*
* @version 1.0.0 2021/9/6 18:22
* @since 1.0.0
*/
@Component("test")
public class Test {
private static final Logger logger = LoggerFactory.getLogger(Test.class);
public void test() throws JobExecutionException {
logger.info("定时任务test():" + new Date());
}
public void testOne() throws JobExecutionException {
logger.info("定时任务testOne():" + new Date());
}
}
本博客只适用于研究学习为目的,大多为学习笔记,如有错误欢迎指正,如有误导敬请谅解。