SpringBoot使用Quartz实现定时任务动态配置


前言

SpringBoot使用quartz实现定时任务动态配置,实现在后台增删改查数据库中配置的定时任务,服务中实时动态更新


一、pom导包

        <!-- quartz 任务调用系统-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>

二、所需工具类

1.ExecutionJob

代码如下(示例):

import cn.hutool.core.thread.ThreadUtil;
import com.program.maintenance.bean.LogSysQuartz;
import com.program.maintenance.bean.SysQuartzJob;
import com.program.maintenance.dao.LogSysQuartzDao;
import com.program.maintenance.service.InitDataService;
import com.program.maintenance.service.ProceduresConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.concurrent.Future;

@Async
@Slf4j
public class ExecutionJob extends QuartzJobBean {
    
    

    @Override
    protected void executeInternal(JobExecutionContext context) {
    
    
        SysQuartzJob quartzJob = (SysQuartzJob) context.getMergedJobDataMap().get(QuartzManage.JOB_KEY);
        // 获取spring bean
        LogSysQuartzDao logSysQuartzDao = SpringContextHolder.getBean(LogSysQuartzDao.class);
        ProceduresConfigService sysQuartzJobService = SpringContextHolder.getBean(ProceduresConfigService.class);
        InitDataService initDataService = SpringContextHolder.getBean(InitDataService.class);
        String uuid = String.valueOf(quartzJob.getId());
        //这个是定时任务执行日志
        LogSysQuartz logSysQuartz = new LogSysQuartz();
        logSysQuartz.setJobId(quartzJob.getId());
        logSysQuartz.setJobName(quartzJob.getJobName());
        logSysQuartz.setBeanName(quartzJob.getBeanName());
        logSysQuartz.setMethodName(quartzJob.getMethodName());
        logSysQuartz.setParams(quartzJob.getParams());
        logSysQuartz.setCronExpression(quartzJob.getCronExpression());
        long startTime = System.currentTimeMillis();
        try {
    
    
            // 执行任务
            QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(),
                    quartzJob.getParams());
            Future<Object> future = ThreadUtil.execAsync(task);
            future.get();
            long times = System.currentTimeMillis() - startTime;
            logSysQuartz.setTime(times);
            if (StringUtils.isNotBlank(uuid)) {
    
    
                initDataService.putTask(uuid, true);
            }
            // 任务状态
            logSysQuartz.setIsSuccess(1);
        } catch (Exception e) {
    
    
            if (StringUtils.isNotBlank(uuid)) {
    
    
                initDataService.putTask(uuid, false);
            }
            long times = System.currentTimeMillis() - startTime;
            logSysQuartz.setTime(times);
            // 任务状态 1:成功 0:失败
            logSysQuartz.setIsSuccess(0);
            logSysQuartz.setExceptionDetail(e.getMessage());
            log.error("【定时任务】定时任务执行失败 =={} == {}", quartzJob.getJobName(), e.getMessage());
            e.printStackTrace();
            // 任务如果失败了则暂停
            if (quartzJob.getPauseAfterFailure() != null && quartzJob.getPauseAfterFailure() == 1) {
    
    
                quartzJob.setIsPause(1);
                //更新状态
                sysQuartzJobService.updateIsPause(quartzJob);
            }
        } finally {
    
    
        	//新增定时任务执行日志,这个dao层代码我就不贴了
            logSysQuartzDao.insertSelective(logSysQuartz);
        }
    }

2.QuartzManage

代码如下(示例):

import com.program.maintenance.bean.SysQuartzJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;

import static org.quartz.TriggerBuilder.newTrigger;


@Slf4j
@Component
public class QuartzManage {
    
    

    private static final String JOB_NAME = "TASK_";
    public static final String JOB_KEY = "JOB_KEY";

    @Resource(name = "scheduler")
    private Scheduler scheduler;

    public void addJob(SysQuartzJob quartzJob) {
    
    
        try {
    
    
            // 构建job信息
            JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class).
                    withIdentity(JOB_NAME + quartzJob.getId()).build();
            //通过触发器名和cron 表达式创建 Trigger
            Trigger cronTrigger = newTrigger()
                    .withIdentity(JOB_NAME + quartzJob.getId())
                    .startNow()
                    .withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
                    .build();

            cronTrigger.getJobDataMap().put(JOB_KEY, quartzJob);

            //重置启动时间
            ((CronTriggerImpl)cronTrigger).setStartTime(new Date());

            //执行定时任务
            scheduler.scheduleJob(jobDetail,cronTrigger);

            // 暂停任务
            if (quartzJob.getIsPause()==0) {
    
    
                pauseJob(quartzJob);
            }
        } catch (Exception e){
    
    
            log.error("创建定时任务失败:" + quartzJob.getJobName(), e);
        }
    }

    /**
     * 更新job cron表达式
     * @param quartzJob /
     */
    public void updateJobCron(SysQuartzJob quartzJob){
    
    
        try {
    
    
            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 如果不存在则创建一个定时任务
            if(trigger == null){
    
    
                addJob(quartzJob);
                trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            }
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression());
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            //重置启动时间
            ((CronTriggerImpl)trigger).setStartTime(new Date());
            trigger.getJobDataMap().put(JOB_KEY,quartzJob);

            scheduler.rescheduleJob(triggerKey, trigger);
            // 暂停任务
            if (quartzJob.getIsPause()==1) {
    
    
                pauseJob(quartzJob);
            }
        } catch (Exception e){
    
    
            log.error("更新定时任务失败", e);
        }

    }

    /**
     * 删除一个job
     * @param quartzJob /
     */
    public void deleteJob(SysQuartzJob quartzJob){
    
    
        try {
    
    
            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
            scheduler.pauseJob(jobKey);
            scheduler.deleteJob(jobKey);
        } catch (Exception e){
    
    
            log.error("删除定时任务失败", e);
        }
    }

    /**
     * 恢复一个job
     * @param quartzJob /
     */
    public void resumeJob(SysQuartzJob quartzJob){
    
    
        try {
    
    
            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 如果不存在则创建一个定时任务
            if(trigger == null) {
    
    
                addJob(quartzJob);
            }
            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
            scheduler.resumeJob(jobKey);
        } catch (Exception e){
    
    
            log.error("恢复定时任务失败", e);
        }
    }

    /**
     * 立即执行job
     * @param quartzJob /
     */
    public void runJobNow(SysQuartzJob quartzJob){
    
    
        try {
    
    
            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 如果不存在则创建一个定时任务
            if(trigger == null) {
    
    
                addJob(quartzJob);
            }
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(JOB_KEY, quartzJob);
            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
            scheduler.triggerJob(jobKey,dataMap);
        } catch (Exception e){
    
    
            log.error("定时任务执行失败", e);
        }
    }

    /**
     * 暂停一个job
     * @param quartzJob /
     */
    public void pauseJob(SysQuartzJob quartzJob){
    
    
        try {
    
    
            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
            scheduler.pauseJob(jobKey);
        } catch (Exception e){
    
    
            log.error("定时任务暂停失败", e);
        }
    }


}

3.QuartzRunnable

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;


@Slf4j
public class QuartzRunnable implements Callable<Object> {
    
    

    private final Object target;
    private final Method method;
    private final String params;

    QuartzRunnable(String beanName, String methodName, String params)
            throws NoSuchMethodException, SecurityException {
    
    
        this.target = SpringContextHolder.getBean(beanName);
        this.params = params;

        if (StringUtils.isNotBlank(params)) {
    
    
            this.method = target.getClass().getDeclaredMethod(methodName, String.class);
        } else {
    
    
            this.method = target.getClass().getDeclaredMethod(methodName);
        }
    }
    @Override
    public Object call() throws Exception {
    
    
        ReflectionUtils.makeAccessible(method);
        if (StringUtils.isNotBlank(params)) {
    
    
            method.invoke(target, params);
        } else {
    
    
            method.invoke(target);
        }
        return null;
    }

}

4.SpringContextHolder

import com.program.common.util.CallBack;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Component
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
    
    

    private static ApplicationContext applicationContext = null;
    private static final List<CallBack> CALL_BACKS = new ArrayList<>();
    private static boolean addCallback = true;

    /**
     * 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。
     * 在SpringContextHolder 初始化后,进行回调使用
     *
     * @param callBack 回调函数
     */
    public synchronized static void addCallBacks(CallBack callBack) {
    
    
        if (addCallback) {
    
    
            SpringContextHolder.CALL_BACKS.add(callBack);
        } else {
    
    
            log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
            callBack.executor();
        }
    }

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
    
    
        assertContextInjected();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> requiredType) {
    
    
        assertContextInjected();
        return applicationContext.getBean(requiredType);
    }

    /**
     * 获取SpringBoot 配置信息
     *
     * @param property     属性key
     * @param defaultValue 默认值
     * @param requiredType 返回类型
     * @return /
     */
    public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {
    
    
        T result = defaultValue;
        try {
    
    
            result = getBean(Environment.class).getProperty(property, requiredType);
        } catch (Exception ignored) {
    
    }
        return result;
    }

    /**
     * 获取SpringBoot 配置信息
     *
     * @param property 属性key
     * @return /
     */
    public static String getProperties(String property) {
    
    
        return getProperties(property, null, String.class);
    }

    /**
     * 获取SpringBoot 配置信息
     *
     * @param property     属性key
     * @param requiredType 返回类型
     * @return /
     */
    public static <T> T getProperties(String property, Class<T> requiredType) {
    
    
        return getProperties(property, null, requiredType);
    }

    /**
     * 检查ApplicationContext不为空.
     */
    private static void assertContextInjected() {
    
    
        if (applicationContext == null) {
    
    
            throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
                    ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
        }
    }

    /**
     * 清除SpringContextHolder中的ApplicationContext为Null.
     */
    private static void clearHolder() {
    
    
        log.debug("清除SpringContextHolder中的ApplicationContext:"
                + applicationContext);
        applicationContext = null;
    }

    @Override
    public void destroy() {
    
    
        SpringContextHolder.clearHolder();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        if (SpringContextHolder.applicationContext != null) {
    
    
            log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
        }
        SpringContextHolder.applicationContext = applicationContext;
        if (addCallback) {
    
    
            for (CallBack callBack : SpringContextHolder.CALL_BACKS) {
    
    
                callBack.executor();
            }
            CALL_BACKS.clear();
        }
        SpringContextHolder.addCallback = false;
    }
}

三、所需配置类

1.JobRunner

import com.program.maintenance.bean.SysQuartzJob;
import com.program.maintenance.dao.SysQuartzJobDao;
import com.program.maintenance.util.QuartzManage;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

@Component
@RequiredArgsConstructor
public class JobRunner implements ApplicationRunner {
    
    

    private static final Logger log = LoggerFactory.getLogger(JobRunner.class);

    @Resource
    private SysQuartzJobDao sysQuartzJobDao;

    @Resource
    private QuartzManage quartzManage;

    /**
     * 项目启动时重新激活启用的定时任务
     *
     * @param applicationArguments /
     */
    @Override
    public void run(ApplicationArguments applicationArguments) {
    
    
        List<SysQuartzJob> quartzJobs = sysQuartzJobDao.getByIsPause(1);
        log.info("--------------------注入定时任务中---------------------");
        quartzJobs.forEach(quartzManage::addJob);
        for (SysQuartzJob quartzJob : quartzJobs) {
    
    
            log.info("定时任务:" + quartzJob.getJobName());
        }
        log.info("--------------------定时任务注入完成---------------------");
    }

2.QuartzConfig


import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;
@Slf4j
@Configuration
public class QuartzConfig {
    
    

    /**
     * 解决Job中注入Spring Bean为null的问题
     */
    @Component("quartzJobFactory")
    public static class QuartzJobFactory extends AdaptableJobFactory {
    
    

        private final AutowireCapableBeanFactory capableBeanFactory;

        public QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) {
    
    
            this.capableBeanFactory = capableBeanFactory;
        }

        @Override
        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
    
    

            //调用父类的方法
            Object jobInstance = super.createJobInstance(bundle);
            capableBeanFactory.autowireBean(jobInstance);
            return jobInstance;
        }
    }

    /**
     * 注入scheduler到spring
     * @param quartzJobFactory /
     * @return Scheduler
     * @throws Exception /
     */
    @Bean(name = "scheduler")
    public Scheduler scheduler(QuartzJobFactory quartzJobFactory) throws Exception {
    
    
        SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
        factoryBean.setJobFactory(quartzJobFactory);
        factoryBean.afterPropertiesSet();
        Scheduler scheduler=factoryBean.getScheduler();
        scheduler.start();
        return scheduler;
    }

}

三、其他代码

这部分的表结构映射实体也可以根据自己的需要去进行修改,不一定要和我一样;

1.LogSysQuartz

/**
 * 定时任务执行日志表
 */
@Data
public class LogSysQuartz {
    
    
    /**
     * id(系统定时任务日志)
     */
    private Integer id;

    /**
     * 系统定时任务id
     */
    private Integer jobId;

    /**
     * Spring Bean名称
     */
    private String beanName;

    /**
     * 创建时间
     */
    private String createTime;

    /**
     * cron 表达式
     */
    private String cronExpression;

    /**
     * 异常详情
     */
    private String exceptionDetail;

    /**
     * 是否成功(0否1是)
     */
    private Integer isSuccess;

    /**
     * 任务名称
     */
    private String jobName;

    /**
     * 方法名称
     */
    private String methodName;

    /**
     * 参数
     */
    private String params;

    /**
     * 执行时间
     */
    private Long time;

    private Integer sysType;
}

2.LogSysQuartz

/**
    * 定时任务配置表
    */
@Data
public class SysQuartzJob{
    
    
    /**
    * id(系统定时任务表)
    */
    private Integer id;

    /**
    * Spring Bean名称
    */
    private String beanName;

    /**
    * cron 表达式
    */
    private String cronExpression;

    /**
    * 是否暂停状态:1是、0否
    */
    private Integer isPause;

    /**
    * 任务名称
    */
    private String jobName;

    /**
    * 方法名称
    */
    private String methodName;

    /**
    * 参数
    */
    private String params;

    /**
    * 任务失败后是否暂停(0否1是)
    */
    private Integer pauseAfterFailure;

    /**
    * 备注
    */
    private String remark;

    /**
    * 删除标志(0正常1删除)
    */
    private Integer del;
}

3.InitDataService

@Service
@Slf4j
public class InitDataService {
    
    
    /**
     * 任务执行状态 Map<任务id,是否正常执行>
     */
    private static final Map<String, Boolean> TASK_MAP = new ConcurrentHashMap<>();




    /**
     * 设置任务状态
     *
     * @param uuid   uuid
     * @param status 任务运行状态
     */
    public void putTask(String uuid, boolean status) {
    
    
        TASK_MAP.put(uuid, status);
    }

    /**
     * 获取任务状态
     *
     * @param uuid uuid
     * @return true正常,false异常
     */
    public Boolean getTask(String uuid) {
    
    
        return TASK_MAP.get(uuid);
    }

    /**
     * 删除任务状态
     *
     * @param uuid uuid
     */
    public void delTask(String uuid) {
    
    
        TASK_MAP.remove(uuid);
    }
}

4.项目启动类

启动类上记得加上这几个注解;

@SpringBootApplication
@EnableAsync
@EnableTransactionManagement
@EnableScheduling
public class MaintenanceApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(MaintenanceApplication.class, args);
    }

}

5.定时任务业务类

@Slf4j
@Component
public class WeChatAccessTokenTask {
    
    



    public void updateWxAccessToken(){
    
    
        log.info("定时更新微信Token执行");
    }


}

这里需要注意下:因为本文使用的是映射机制,所以注意下类名称和方法名称,下方是我的数据库配置的示例,可以参考下,包名称和类名称不要大写
在这里插入图片描述

这个下面的是定时任务执行日志表
在这里插入图片描述

5.项目结构目录

在这里插入图片描述

四、最后结果

在这里插入图片描述


总结

该用到的代码都贴上了,有啥不清楚的可以下方评论,有看到的都会及时回复

猜你喜欢

转载自blog.csdn.net/qq_42666609/article/details/133375580