快速使用quartz 定时任务

前瞻

导入依赖

      <!--引入quartz定时框架-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

先了解几个概念:

  • Job,即要定时执行的 工作,要继承QuartzJobBean类
  • trigger,触发器,用来触发Job
  • Schedule,定时的时间表

我们可能有很多个定时任务,并且可能会随时更改,所以我们是肯定不能编写很多个Job,这样耦合太大。

所以我们应该再编写一些Task,让Task作为我们真正去执行的定时任务的代码。然后用一个Job去调用Task,并在task执行完毕后进行一些log记录等等操作。这样就能得到低耦合的代码

然后所有的Task的信息都应该放在数据库中,方便管理保存

那么Job整么知道要去执行哪个task呢?

Job继承了QuartzJobBean类,要实现这个方法

executeInternal(JobExecutionContext context)

其中context参数就可以给我们提供这些信息

初步理解过程

我们先拿手动触发一次定时任务为例

前端给我们传来了要执行的定时任务的id,我们再根据id查询定时任务的具体信息

    public void runJobById(Long jobId) {
        ScheduleUtils.run(scheduler, schedulerJobMapper.getJobById(jobId));
    }

其中scheduler是SpringBoot帮我们创建好的bean,我们只需要注入一下就行。我们要靠他才能调用定时任务

    @Autowired
    Scheduler scheduler;

查询用的实体类

public class ScheduleJob {
    public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY"; //任务调度参数key

    private Long jobId;//任务id
    private String beanName;//spring bean名称
    private String methodName;//方法名
    private String params;//参数
    private String cron;//cron表达式
    private Boolean status;//任务状态
    private String remark;//备注
    private Date createTime;//创建时间
}

手动触发的方法

    public static void run(Scheduler scheduler, ScheduleJob scheduleJob) {
        try {
            //参数
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(ScheduleJob.JOB_PARAM_KEY, scheduleJob);
            scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap);
        } catch (SchedulerException e) {
            throw new RuntimeException("立即执行定时任务失败", e);
        }
    }

可以看到我们把task的相关信息放到了Map中,在scheduler调用triggerJob时把要执行的Job

public class ScheduleJob extends QuartzJobBean {
    private ExecutorService service = Executors.newSingleThreadExecutor();

    @Override
    protected void executeInternal(JobExecutionContext context) {

        top.naccl.entity.ScheduleJob scheduleJob = (top.naccl.entity.ScheduleJob) context.getMergedJobDataMap().get(top.naccl.entity.ScheduleJob.JOB_PARAM_KEY);

        //获取spring bean
        ScheduleJobService scheduleJobService = (ScheduleJobService) SpringContextUtils.getBean("scheduleJobServiceImpl");
        //数据库保存任务执行记录
        ScheduleJobLog jobLog = new ScheduleJobLog();
        jobLog.setJobId(scheduleJob.getJobId());
        jobLog.setBeanName(scheduleJob.getBeanName());
        jobLog.setMethodName(scheduleJob.getMethodName());
        jobLog.setParams(scheduleJob.getParams());
        jobLog.setCreateTime(new Date());
        //任务开始时间
        long startTime = System.currentTimeMillis();

        //执行任务
        log.info("任务准备执行,任务ID:{}", scheduleJob.getJobId());
        try {
            ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getBeanName(), scheduleJob.getMethodName(), scheduleJob.getParams());
            Future<?> future = service.submit(task);
            future.get();
            //任务执行总时长
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes((int) times);
            //任务执行结果
            jobLog.setStatus(true);
            log.info("任务执行成功,任务ID:{},总共耗时:{} 毫秒", scheduleJob.getJobId(), times);
        } catch (Exception e) {
            //任务执行总时长
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes((int) times);
            //任务执行结果
            jobLog.setStatus(false);
            jobLog.setError(e.toString());
            log.error("任务执行失败,任务ID:{}", scheduleJob.getJobId(), e);
        } finally {
            scheduleJobService.saveJobLog(jobLog);
        }
    }
}

ScheduleJob是将实际的操作交给了ScheduleRunnable来进行的。

public class ScheduleRunnable implements Runnable {
    private Object target;
    private Method method;
    private String params;

    public ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
        this.target = SpringContextUtils.getBean(beanName);
        this.params = params;
        if (StringUtils.hasText(params)) {
            this.method = target.getClass().getDeclaredMethod(methodName, String.class);
        } else {
            this.method = target.getClass().getDeclaredMethod(methodName);
        }
    }

    @Override
    public void run() {
        try {
            ReflectionUtils.makeAccessible(method);
            if (StringUtils.hasText(params)) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception e) {
            throw new RuntimeException("执行定时任务失败", e);
        }
    }
}

现在我们就实现了只通过ScheduleJob一个继承了QuartzJobBean类 就能处理多个不同的定时任务。

是利用了反射去执行这些Task的

容易混淆的地方

上面的代码中有一个继承了QuartzJobBean的ScheduleJob和实体类的ScheduleJob,只是名字一样而已,不要搞混了

完整的实现

从上面的代码中我们已经实现了将数据库中得到Job信息,并通过ScheduleJob这个继承了QuartzJobBean类来进行定时任务。

现在我们在系统初始化的时候就把Task的信息放到schedule中。

    @PostConstruct
    public void init() {
        List<ScheduleJob> scheduleJobList = getJobList();
        for (ScheduleJob scheduleJob : scheduleJobList) {
            CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId());

            //如果不存在,则创建
            if (cronTrigger == null) {
                ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
            } else {
                ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
            }
        }
    }

创建

    public static void createScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) {
        try {
            //构建job信息
        // 	构建时Job都是用的top.naccl.util.quartz.ScheduleJob.class,只是Identity用的是不同Jobkey
        // 这样无论我们调用的是哪个Identity的Job,最终都会调用top.naccl.util.quartz.ScheduleJob.class
            JobDetail jobDetail = JobBuilder.newJob(top.naccl.util.quartz.ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId())).build();
            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCron()).withMisfireHandlingInstructionDoNothing();
            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getJobId())).withSchedule(scheduleBuilder).build();
            //放入参数,运行时的方法可以获取
            jobDetail.getJobDataMap().put(ScheduleJob.JOB_PARAM_KEY, scheduleJob);
            scheduler.scheduleJob(jobDetail, trigger);
            if (!scheduleJob.getStatus()) {
                pauseJob(scheduler, scheduleJob.getJobId());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException("创建定时任务失败", e);
        }
    }

暂停

scheduler.pauseJob(getJobKey(jobId));

更新操作

    public static void updateScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) {
        try {
            TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId());
            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCron()).withMisfireHandlingInstructionDoNothing();
            CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getJobId());
            //按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            //参数
            trigger.getJobDataMap().put(ScheduleJob.JOB_PARAM_KEY, scheduleJob);
            scheduler.rescheduleJob(triggerKey, trigger);
            if (!scheduleJob.getStatus()) {
                pauseJob(scheduler, scheduleJob.getJobId());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException("更新定时任务失败", e);
        }
    }
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值