SpringBoot + Quartz 实现动态新增和删除定时任务

最近需要做一个简单的监控系统,需要定时任务查询不同数据库表的数据是否正常,需要动态的增加定时任务与监控SQL,这里简单记一下

1、 pom.xml 依赖配置

这里用的springboot 是1.5.10,如果是2.x版本已经集成了quartz


        org.springframework.boot
        spring-boot-starter-parent
        1.5.10.RELEASE
        
    

            org.quartz-scheduler
            quartz
            2.3.0
        

2、实体类 SchedulerJobEntity

@Data
public class SchedulerJobEntity extends BaseEntity {

    private static final long serialVersionUID = 6678656140297128519L;

    private String jobName;

    private String beanName;

    private String methodName;

    private String params;

    private String cronExpression;

    private String status;

    private String remark;

}

3、ScheduleJob 和 JobContext

任务执行时,根据SchedulerJobEntity中的类名和方法名称获取需要执行的定时任务,JobContext用于参数传递

@Data
public class JobContext implements Serializable {

    private static final long serialVersionUID = -8127498514244724662L;

    /**
     * 任务ID
     */
    private Integer jobId;

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

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

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

    /**
     * 如果{@link SchedulerJobEntity#getParams()}是json格式,那么会把里面的值存入此map,这里也可以加入其它参数
     */
    private Map paramMap = new HashMap<>();
    /**
     * 执行结果
     */
    private Map resultMap = new HashMap<>();
}
@Slf4j
public class ScheduleJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        SchedulerJobEntity jobEntity = (SchedulerJobEntity) context.getMergedJobDataMap().get(JOB_PARAM_KEY);
        SchedulerJobLogService scheduleJobLogService = SpringUtil.getBean(SchedulerJobLogService.class);

        SchedulerJobLogEntity logEntity = new SchedulerJobLogEntity();
        logEntity.setJobId(jobEntity.getId());
        logEntity.setJobName(jobEntity.getJobName());
        logEntity.setParams(jobEntity.getParams());
        logEntity.setCreated(new Date());

        long startTime = System.currentTimeMillis();

        try {
            MDC.put(Constant.JC_REQ_ID, LogUtil.generateReqId());
            log.info("任务准备执行,任务ID:{}, 名称={}", jobEntity.getId(), jobEntity.getJobName());
            doTask(jobEntity);
            long times = System.currentTimeMillis() - startTime;
            logEntity.setTimePeriod((int) times);
            logEntity.setStatus(TaskStatus.success.name());
            log.info("任务执行完毕,任务ID:{}, 名称={}  总共耗时:{}毫秒", jobEntity.getId(), jobEntity.getJobName(), times);
            if (times > 5000) {
                // 超长时间的任务记录日志
                scheduleJobLogService.save(logEntity);
            }
        } catch (Exception e) {
            log.error("任务执行失败,任务ID:{}, 名称={}", jobEntity.getId(), jobEntity.getJobName(), e);
            long times = System.currentTimeMillis() - startTime;
            logEntity.setTimePeriod((int) times);
            logEntity.setStatus(TaskStatus.failure.name());
            logEntity.setErrorMsg(StringUtils.left(ExceptionUtils.getStackTrace(e), 255));
            scheduleJobLogService.save(logEntity);
        }
    }

    private void doTask(SchedulerJobEntity jobEntity) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        String beanName = jobEntity.getBeanName();
        Object target;
        if (StringUtils.isEmpty(beanName)) {
            target = SpringUtil.getBean(MonitorTask.class);
        } else {
            target = SpringUtil.getBean(beanName);
        }
        String methodName = StringUtils.defaultIfBlank(jobEntity.getMethodName(), "monitorJob");
        Method method = target.getClass().getDeclaredMethod(methodName, JobContext.class);
        JobContext jobContext = JoddBeanUtil.copyBean(jobEntity, JobContext.class);
        if(StringUtils.isNotEmpty(jobEntity.getParams())) {
            try {
                jobContext.getParamMap().putAll(JSON.parseObject(jobEntity.getParams()));
            } catch (Exception e) {
                log.error("参数解析失败,params不是JSON 格式,params={}", jobEntity.getParams(), e);
            }
        }
        jobContext.setJobId(jobEntity.getId());
        method.invoke(target, jobContext);
    }
}

4 SchedulerUtil 用于任务管理的工具类

public class SchedulerUtils {

    private final static String JOB_NAME = "TASK_";

    public final static String JOB_PARAM_KEY = "TASK_PARAM_";


    private static TriggerKey getTriggerKey(Integer jobId) {
        return TriggerKey.triggerKey(JOB_NAME + jobId);
    }


    private static JobKey getJobKey(Integer jobId) {
        return JobKey.jobKey(JOB_NAME + jobId);
    }


    public static CronTrigger getCronTrigger(Scheduler scheduler, Integer jobId) {
        try {
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("获取定时任务CronTrigger出现异常", e);
        }
    }


    public static void createSchedulerJob(Scheduler scheduler, SchedulerJobEntity jobEntity) {
        try {
            JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(jobEntity.getId())).build();

            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobEntity.getCronExpression())
                .withMisfireHandlingInstructionDoNothing();

            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobEntity.getId())).withSchedule(scheduleBuilder)
                .build();

            jobDetail.getJobDataMap().put(JOB_PARAM_KEY, jobEntity);

            scheduler.scheduleJob(jobDetail, trigger);

            if (!StatusEnum.online.name().equalsIgnoreCase(jobEntity.getStatus())) {
                pauseJob(scheduler, jobEntity.getId());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException("创建定时任务失败", e);
        }
    }


    public static void updateSchedulerJob(Scheduler scheduler, SchedulerJobEntity jobEntity) {
        try {
            TriggerKey triggerKey = getTriggerKey(jobEntity.getId());

            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobEntity.getCronExpression())
                .withMisfireHandlingInstructionDoNothing();

            CronTrigger trigger = getCronTrigger(scheduler, jobEntity.getId());

            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

            trigger.getJobDataMap().put(JOB_PARAM_KEY, jobEntity);

            scheduler.rescheduleJob(triggerKey, trigger);

            if (!StatusEnum.online.name().equalsIgnoreCase(jobEntity.getStatus())) {
                pauseJob(scheduler, jobEntity.getId());
            }

        } catch (SchedulerException e) {
            throw new RuntimeException("更新定时任务失败", e);
        }
    }


    public static void run(Scheduler scheduler, Integer jobId) {
        try {
            scheduler.triggerJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("立即执行定时任务失败", e);
        }
    }


    public static void pauseJob(Scheduler scheduler, Integer jobId) {
        try {
            scheduler.pauseJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("暂停定时任务失败", e);
        }
    }


    public static void resumeJob(Scheduler scheduler, Integer jobId) {
        try {
            scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("恢复定时任务失败", e);
        }
    }


    public static void deleteScheduleJob(Scheduler scheduler, Integer jobId) {
        try {
            scheduler.deleteJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("删除定时任务失败", e);
        }
    }
}

5、SchedulerJobBiz

项目启动时初始化任务,任务增删改查的时候同步修改任务计划

@Slf4j
@Service
public class SchedulerJobBizImpl implements SchedulerJobBiz {

    @Resource
    private SchedulerJobService schedulerJobService;

    @Resource
    private Scheduler scheduler;

    /**
     * 初始化任务
     */
    @PostConstruct
    public void init() {
        List scheduleJobList = schedulerJobService.list();
        for (SchedulerJobEntity jobEntity : scheduleJobList) {
            try {
                CronTrigger cronTrigger = SchedulerUtils.getCronTrigger(scheduler, jobEntity.getId());
                if (cronTrigger == null) {
                    SchedulerUtils.createSchedulerJob(scheduler, jobEntity);
                } else {
                    SchedulerUtils.updateSchedulerJob(scheduler, jobEntity);
                }
            } catch (Exception e) {
                log.error("启动任务失败", e);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void save(SchedulerJobEntity jobEntity) {
        jobEntity.setStatus(StatusEnum.online.name());
        schedulerJobService.save(jobEntity);
        SchedulerUtils.createSchedulerJob(scheduler, jobEntity);
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void update(SchedulerJobEntity jobEntity) {
        schedulerJobService.updateById(jobEntity);
        SchedulerUtils.updateSchedulerJob(scheduler, jobEntity);
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void delete(SchedulerJobEntity jobEntity) {
        SchedulerUtils.deleteScheduleJob(scheduler, jobEntity.getId());
        jobEntity.setStatus(StatusEnum.offline.name());
        jobEntity.setDeleted(new Date());
        schedulerJobService.updateById(jobEntity);
    }

    @Override
    public void run(Integer[] jobIds) {
        for (Integer jobId : jobIds) {
            SchedulerUtils.run(scheduler, jobId);
        }
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void pause(Integer[] jobIds) {
        for (Integer jobId : jobIds) {
            SchedulerUtils.pauseJob(scheduler, jobId);
        }
        updateBatch(jobIds, StatusEnum.offline);
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void resume(Integer[] jobIds) {
        for (Integer jobId : jobIds) {
            SchedulerUtils.resumeJob(scheduler, jobId);
        }
        updateBatch(jobIds, StatusEnum.online);
    }

    private void updateBatch(Integer[] jobIds, StatusEnum status) {
        schedulerJobService.batchUpdateStatus(jobIds, status.name());
    }
}

6、SchedulerConfig 配置

定时任务的基本配置

@Configuration
public class SchedulerConfig {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "JcMonitorScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "15");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        factory.setQuartzProperties(prop);
        factory.setSchedulerName("JcMonitorScheduler");
        factory.setStartupDelay(30);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        factory.setOverwriteExistingJobs(true);
        //这样当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
        factory.setWaitForJobsToCompleteOnShutdown(true);
        factory.setAutoStartup(true);
        return factory;
    }

}

这里controller类和dao层代码没有贴出来。

你可能感兴趣的:(JAVA)