使用quartz实现动态定时任务并且记录任务调度日志

目录

quartz概述

springboot使用quartz

导入依赖

数据库脚本

配置类:QuartzConfig

定时任务job工厂:JobFactory

监听类:ScheduleJobInitListener

抽象类:AbstractQuartzJob

具体实现类:QuartzJobExecution

工具类:JobInvokeUtils

工具类:BeansUtils

定时任务管理类:QuartzManager

定时任务service实现类

定时任务service

测试类

执行结果


quartz概述

Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现。该项目于 2009 年被 Terracotta 收购,目前是 Terracotta 旗下的一个项目。读者可以到 http://www.quartz-scheduler.org/站点下载 Quartz 的发布版本及其源代码。

springboot使用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;

配置类:QuartzConfig

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(); } }

定时任务job工厂:JobFactory

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; } }

监听类:ScheduleJobInitListener

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(); } } }

抽象类:AbstractQuartzJob

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; }

具体实现类:QuartzJobExecution

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); } }

工具类:JobInvokeUtils

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); } }

工具类:BeansUtils

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); } }

定时任务管理类:QuartzManager

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 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 jobClass = getQuartzJobClass(task); // Class jobClass = (Class) (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); } }

定时任务service实现类

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;
    }
}

定时任务service

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()); } }

执行结果

结束语

本博客只适用于研究学习为目的,大多为学习笔记,如有错误欢迎指正,如有误导敬请谅解。

你可能感兴趣的:(java,quartz,定时任务,spring,boot)