SpringBoot uses Quartz to implement dynamic configuration of scheduled tasks


Preface

SpringBoot uses quartz to implement dynamic configuration of scheduled tasks, add, delete, modify, and query scheduled tasks configured in the database in the background, and dynamically update the service in real time.


1. pom guide package

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

2. Required tools

1.ExecutionJob

The code is as follows (example):

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

The code is as follows (example):

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

3. Required configuration categories

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

}

3. Other codes

This part of the table structure mapping entity can also be modified according to your own needs, and it does not have to be the same as mine;

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.Project startup category

Remember to add these annotations to the startup class;

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

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

}

5. Scheduled task business class

@Slf4j
@Component
public class WeChatAccessTokenTask {
    
    



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


}

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

Below this is the scheduled task execution log table
Insert image description here

5.Project structure directory

Insert image description here

4. Final result

Insert image description here


Summarize

All the codes that need to be used are posted. If you have any questions, please leave a comment below. If you see anything, we will reply promptly.

Guess you like

Origin blog.csdn.net/qq_42666609/article/details/133375580