使用Quartz框架集成Spring,动态配置定时任务(个人思考)
Posted 张子行的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Quartz框架集成Spring,动态配置定时任务(个人思考)相关的知识,希望对你有一定的参考价值。
前言
最近遇到这样一个需求,需要开发一个类似若依的任务管理控制面板。核心内容是让用户可以动态的去进行对任务的增、删、改、查操作。实现方式有很多种下面先来简单列举一下可实现这个需求的技术。
- 使用Spring为我们提供的@Scheduled注解可以生成定时任务,只要我反射学的足够NB,动态的修改定时任务的参数也不是什么难事(自己造轮子、不推荐)
- 使用XXL-JOB这个技术框架,XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。(以后抽空学习这个框架)
- 使用Quartz框架,利用其中为我们提供的API轻松解决,前提是对Spring有一定的理解才行,好了不多BB,下文基于这个框架来铺述
正文
先来简单描述一下Quartz中的三大基本组件吧。
- 调度器工厂(SchedulerFactory):顾名思义用来生产调度器的,调度器负责控制所有旗下任务的运行
- 触发器(Trigger):用来设置任务的触发条件
- 任务类型(JobDetail):自定义任务的类型,只需传入对应类的Class即可
接下来一个小例子生动形象的诠释上面的三大组件:我帮老师创建一个改作业的任务(确定JobDetail的类型),当有作业上交到办公室的时候(确定任务触发时机),帮老师改作业(由调度器调度任务的状态),这样老师就轻松一点了。
需求分析
我们需要提供一套对任务的CRUD接口出来,那么SchedulerFactory必然要交给Spring Ioc容器管理撒,其次我们要对任务的信息做一个存储,这样我们才能对具体的某个任务进行CRUD,就这么简单,接下来敲代码,嘿嘿
编码阶段
我坦白了,不装了这里我提供一个我写的对任务的工具类,这种工具类有俩种写法,下面的是写法一,由于我知道Spring-Ioc底层的源码实现中有这么一步操作:会对即将生成的Bean会进行属性填充,也就是会通过Setter方法对Bean中的属性填充赋值,如果我们在Setter方法上加上了@Autowired注解,那么方法参数的来源是从Ioc容器中获取,如果Ioc容器中都没有,那么会进行CreateBean()的操作(具体的源码运行我有点扯多了),总之方法参数schedulerFactory是从Ioc容器中获取的,如下的代码就实现了SchedulerFactory 的自动注入,以及对Scheduler 的初始化。通过如下的工具类,我们可以很方便的对任务进行CRUD。
@Component
public class JobUtil implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext;
private static Scheduler scheduler;
@Autowired
public void setScheduler(SchedulerFactory schedulerFactory) throws SchedulerException {
JobUtil.scheduler = schedulerFactory.getScheduler();
}
@SneakyThrows
public static void addJob(Class orderJob, Trigger trigger, JobDataMap jobDataMap) {
JobDetail jobDetail = JobBuilder.newJob().ofType(orderJob).setJobData(jobDataMap).build();
scheduler.scheduleJob(jobDetail, trigger);
}
@SneakyThrows
public static void pauseJob(String jobName, String jobGroup) {
JobKey jobKey = new JobKey(jobName, jobGroup);
scheduler.pauseJob(jobKey);
}
@SneakyThrows
public static void resumeJob(String jobName, String jobGroup) {
JobKey jobKey = new JobKey(jobName, jobGroup);
scheduler.resumeJob(jobKey);
}
@SneakyThrows
public static void deleteJob(String jobName, String jobGroup) {
JobKey jobKey = new JobKey(jobName, jobGroup);
scheduler.deleteJob(jobKey);
}
@SneakyThrows
public static JobDetail getJob(String jobName, String jobGroup) {
JobKey jobKey = new JobKey(jobName, jobGroup);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
return jobDetail;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void destroy() {
JobUtil.applicationContext = null;
}
}
工具类采用Setter方式赋值原因
可能很多小伙伴,不理解我为什么要通过Setter方式进行Bean注入的,JobUtil工具类不是已经实现了ApplicationContextAware接口吗,你Spring容器的上下文对象都已经有了,为什么不通过如下这种方式对Scheduler进行初始化?笔者有幸学过一点JVM的皮毛,这一切还要从static关键字说起了,被static修饰的变量,在类加载的时候就会被进行赋值,请问这个时候applicationContext从哪里来,到这里又不得不说一下这些Aware接口调用时机了(看下图),Spring的Ioc容器不是万能的!!!!Spring Ioc容器能帮我们进行管理类,但是你最起码对应的类要生成吧。所以 如下这种方式注入,一定会爆空指针异常
private static Scheduler scheduler = applicationContext.getBean(SchedulerFactory.class).getScheduler();
注入方式改进方案
每个工具方法中写上这么一句代码,可是可以,但是代码冗余太高了!
Scheduler scheduler = applicationContext.getBean(SchedulerFactory.class).getScheduler();
@SneakyThrows
public static void addJob(Class orderJob, Trigger trigger, JobDataMap jobDataMap) {
Scheduler scheduler = applicationContext.getBean(SchedulerFactory.class).getScheduler();
JobDetail jobDetail = JobBuilder.newJob().ofType(orderJob).setJobData(jobDataMap).build();
scheduler.scheduleJob(jobDetail, trigger);
}
到这里绝大部分读者可能都已经壁坑了,就怕有人钻牛角尖啊,如果不清楚Spring的源码是怎么运行的恐怕就出不来了,请看下文,牛角尖人员的编码
极端人员编写代码如下
好家伙我也使用Setter方法注入,二而且我还利用到了 ApplicationContext 这个咧,快夸我(__) 嘻嘻,但是这里一定会爆ApplicationContext空指针异常出现,
原因就是Bean的生命周期是这样的,先是进行populateBean(进行Setter方式注入参数Bean)操作然后再是进行initializeBean(调用Aware接口)操作,当前类都还没调用Aware增强接口,ApplicationContext 自然没填充进来的。具体关于Spring源码部分的研究,读者可以看一下我写的这篇文章 spring 源码解析(配图文讲解)顺带搞懂了循环依赖、aop底层实现
@Autowired
public void setScheduler(SchedulerFactory schedulerFactory) throws SchedulerException {
JobUtil.scheduler = applicationContext.getBean(SchedulerFactory.class).getScheduler();
}
好了好了相信大家对这个JobUtil已经是了如指掌了吧,下文开始正式开发 使用Quartz框架集成Spring,动态配置定时任务的这个需求。
接口实现
拿着工具类来写一套CRUD哈哈哈哈哈,但是也需要注意一些细节哦,我们可以利用Quartz为我们提供的JobDataMap这个Map来实现为任务进行传参,具体的实现查看如下的代码: jdk开启定时任务接口。
@Slf4j
@Api(tags = "任务中心")
@RestController
@RequestMapping("job")
public class jobController {
@Autowired
private SchedulerFactory schedulerFactory;
@SneakyThrows
@PostConstruct
public void initJobs() {
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.start();
}
@ApiOperation(value = "jdk开启定时任务", notes = "b")
@GetMapping("/testJob")
public R getNacosConfig() {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("orderId", "1");
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()//立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)//每隔1s执行一次
.withRepeatCount(10000)).build(); //重复1w次
JobUtil.addJob(OrderJob.class, trigger, jobDataMap);
JobKey jobKey = trigger.getJobKey();
return R.ok("success" + jobKey.getName() + "--" + jobKey.getGroup());
}
@ApiOperation(value = "jdk删除定时任务", notes = "b")
@GetMapping("/deleteJob/{jobName}/{jobGroup}")
public R Jobdelete(@PathVariable("jobName") String jobName,
@PathVariable("jobGroup") String jobGroup) {
JobUtil.deleteJob(jobName, jobGroup);
return R.ok("success");
}
@ApiOperation(value = "jdk暂停定时任务", notes = "b")
@GetMapping("/pauseJob/{jobName}/{jobGroup}")
public R JobStop(@PathVariable("jobName") String jobName,
@PathVariable("jobGroup") String jobGroup) {
JobUtil.pauseJob(jobName, jobGroup);
return R.ok("success");
}
@ApiOperation(value = "jdk重启定时任务", notes = "b")
@GetMapping("/resumeJob/{jobName}/{jobGroup}")
public R resumeJob(@PathVariable("jobName") String jobName,
@PathVariable("jobGroup") String jobGroup) {
JobUtil.resumeJob(jobName, jobGroup);
return R.ok("success");
}
}
我们的自定义任务
从开发角度上看任务必然是需要依赖Service层的撒,但是任务就是一个普通的类啊,Service注入不进来啊慌得一批,没事利用上文JobUtil中的ApplicationContext对象做文章获取一下指定的Service就好了,接着进行任务的参数传递到Service层。具体实现看如下代码
public class OrderJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
OrderServiceImpl orderService = JobUtil.getBean(OrderServiceImpl.class);
orderService.payOrder(jobDataMap);
}
}
最终效果
一旦开启定时任务,就会调用业务层的payOrder方法,在这里我们可以实现自己的任务逻辑,十分方便,同时可以随时终止、暂定、删除该任务
本文思考
JobDataMap:我们可以通过JobDataMap将一些业务数据传递到定时任务中,继而定时任务就可以帮我们处理对应的业务了。
个人在写这篇文章的时候一直在思考,这种参数传递一层套一层,让人看着实在不优雅,盲猜一波JobDataMap其实就是通过ThreadLocal来进行传递的,那么我们上文中的代码是不是也能利用Threadlocal将其优化一波呢?本文篇幅有限,以后俺再来研究一波吧。
我是互联网中苟且偷生的一名大四狗,欢迎大家和我交流技术呀哈哈哈哈哈
以上是关于使用Quartz框架集成Spring,动态配置定时任务(个人思考)的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot集成持久化Quartz定时任务管理和界面展示
Spring Boot集成Quartz注入Spring管理的类
SpringBoot系列:Spring Boot集成定时任务Quartz