SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

沙海
沙海
沙海
1017
文章
2
评论
2021年4月21日12:46:00
评论
1 8904字阅读29分40秒
摘要

速读摘要

速读摘要

定时任务实现方式千人千种,不过基础的无外乎1、JDK的Timer类2、Quartz 3、SpringTask~。但是却有个致命的缺点,比如你要改某个任务的触发时间,亦或是你要新增一个任务,暂停一个任务。大功告成,一个简单的动态配置的定时任务已经完成。是不是so easy,下面我们再来简单实现下其他的几种常用的api吧。MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

原文约 4596 | 图片 9 | 建议阅读 10 分钟 | 评价反馈

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

点击关注 ? Java基基

收录于话题

#Java基基

18个

点击上方“Java基基”,选择“设为星标”

做积极的人,而不是积极废人!

源码精品专栏

 

来源:cnblogs.com/laoyeye/p/9352002.html

什么是动态配置定时任务?

大家好,我是基基!

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

首先说下这次主题,动态配置。没接触过定时任务的同学可以先看下此篇:JAVA定时任务实现的几种方式

定时任务实现方式千人千种,不过基础的无外乎 1、JDK 的Timer类 2、Quartz 3、SpringTask  。生产上三种方式我都有使用过。但是使用过程中用的最多的便是xml配置的方式,这种方式最简单,无代码侵入,也比较好理解。但是却有个致命的缺点,比如你要改某个任务的触发时间,亦或是你要新增一个任务,暂停一个任务。怎么做?停应用!改XML配置!重新启动!是不是很致命。最近重新研究了下Quartz的配置,实现了不停机添加、暂停、删除、立即触发任务的方法,在本篇分享出来,其实也不算是完全的研究,在上家公司已经有大佬实现了,这次是也是基于大佬的理解重新实现下。

国际惯例~先看效果图  ps:文末有彩蛋哦~~

管理界面:

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

效果图:实在不知道该跑什么了,每隔十秒打一段话吧

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

技术实现

maven依赖

使用springboot做框架支持

<!-- quartz --><dependency>    <groupId>org.quartz-scheduler</groupId>    <artifactId>quartz</artifactId>    <version>2.2.1</version></dependency><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context-support</artifactId></dependency>

数据表

CREATE TABLE `sys_task` (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `job_name` varchar(255) DEFAULT NULL COMMENT '任务名',  `description` varchar(255) DEFAULT NULL COMMENT '任务描述',  `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron表达式',  `bean_class` varchar(255) DEFAULT NULL COMMENT '任务执行时调用哪个类的方法 包名+类名',  `job_status` varchar(255) DEFAULT NULL COMMENT '任务状态',  `job_group` varchar(255) DEFAULT NULL COMMENT '任务分组',  `create_user` varchar(64) DEFAULT NULL COMMENT '创建者',  `create_time` datetime DEFAULT NULL COMMENT '创建时间',  `update_user` varchar(64) DEFAULT NULL COMMENT '更新者',  `update_time` datetime DEFAULT NULL COMMENT '更新时间',  PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;

实现步骤

①启动项目,启动task监听

②读取数据库,将开启的任务job和trigger加载到scheduler调度器

③根据任务调度运行job类

④每次运行利用AdaptableJobFactory实例化job类,以便注入要运行的service

听着是不是很简单,但却还是一头雾水,且听我慢慢道来~~

代码逻辑

第一步:启动项目,加载监听

Quartz配置

Springboot的配置方法,常规Spring项目可以在xml中配置

@Configurationpublic class QuartzConfigration {    @Autowired    private JobFactory jobFactory;    @Bean    public SchedulerFactoryBean schedulerFactoryBean() {        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();        try {            schedulerFactoryBean.setOverwriteExistingJobs(true);            schedulerFactoryBean.setQuartzProperties(quartzProperties());            schedulerFactoryBean.setJobFactory(jobFactory);        } catch (Exception e) {            e.printStackTrace();        }        return schedulerFactoryBean;    }    // 指定quartz.properties,可在配置文件中配置相关属性    @Bean    public Properties quartzProperties() throws IOException {        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();        propertiesFactoryBean.setLocation(new ClassPathResource("/config/quartz.properties"));        propertiesFactoryBean.afterPropertiesSet();        return propertiesFactoryBean.getObject();    }    // 创建schedule    @Bean(name = "scheduler")    public Scheduler scheduler() {        return schedulerFactoryBean().getScheduler();    }}

监听器

@Component@Order(value = 1)public class ScheduleJobInitListener implements CommandLineRunner {    @Autowired    TaskService scheduleJobService;    @Override    public void run(String... arg0) throws Exception {        try {            scheduleJobService.initSchedule();        } catch (Exception e) {            e.printStackTrace();        }    }}
CommandLineRunner类似Spring框架的ApplicationListener监听器。官方的解释是:

Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple CommandLineRunner beans can be defined within the same application context and can be ordered using the Ordered interface or Order @Order annotation. 接口被用作将其加入spring容器中时执行其run方法。多个CommandLineRunner可以被同时执行在同一个spring上下文中并且执行顺序是以order注解的参数顺序一致。

第二步:读取数据库,加载scheduler调度器

job方法

    @Override    public void initSchedule() throws SchedulerException {        // 这里获取任务信息数据        List<TaskDO> jobList = taskMapper.list();        for (TaskDO task : jobList) {            if (JobStatusEnum.RUNNING.getCode().equals(task.getJobStatus())) {                quartzManager.addJob(task);            }        }    }

添加任务到Quartz调度器

    /**     * 添加任务*/    @SuppressWarnings("unchecked")    public void addJob(TaskDO task) {        try {            // 创建jobDetail实例,绑定Job实现类            // 指明job的名称,所在组的名称,以及绑定job类            Class<? extends Job> jobClass = (Class<? extends Job>) (Class.forName(task.getBeanClass()).newInstance()                    .getClass());            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(task.getJobName(), task.getJobGroup())// 任务名称和组构成任务key                    .build();            // 定义调度触发规则            // 使用cornTrigger规则            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(task.getJobName(), task.getJobGroup())// 触发器key                    .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))                    .withSchedule(CronScheduleBuilder.cronSchedule(task.getCronExpression())).startNow().build();            // 把作业和触发器注册到任务调度中            scheduler.scheduleJob(jobDetail, trigger);            // 启动            if (!scheduler.isShutdown()) {                scheduler.start();            }        } catch (Exception e) {            e.printStackTrace();        }    }

Scheduler作为Quartz的核心调度器,有将近50多个API接口,包括任务的添加,暂停,恢复,删除等一系列的API,这里仅介绍一些常用的,想要了解更多可以稍后看下彩蛋部分。

1、start()方法:只有调用start()方法后,Scheduler线程才开始启动触发器trigger,运行job

2、pauseJob(JobKey jobKey) :根据指定的JobDetail key暂停job。

3、resumeJob(JobKey jobKey) :根据指定的key恢复一个job。

4、deleteJob(JobKey jobKey) :删除一个job

5、triggerJob(JobKey jobKey) :触发一个JobDetail(现在执行)。

6、rescheduleJob(TriggerKey triggerKey, Trigger newTrigger):

用给定的键删除触发器,并存储新的触发器,它必须与同一个作业相关联(新触发器必须具有指定的作业名和组)-然而,新触发器不必具有与旧触发器相同的名称。

第三步:根据任务调度运行job类其实这一步是不需要我们编写的,在我们将正确的JobDetail 和 Trigger 表达式加载到任务调度后,调度器会自动触发任务的执行

第四步:实例化job类,注入要运行的service

工厂类

@Componentpublic class JobFactory extends AdaptableJobFactory {    //这个对象Spring会帮我们自动注入进来,也属于Spring技术范畴.    //为什么需要这个类呢,在我写的这个demo中,大家可以将此类删掉,发现程序也可以正确运行,可是我为什么还是加上呢。    //大家可以看下我们的任务类,大家可以看到Job对象的实例化过程是在Quartz中进行的,这时候我们将spring的东西注入进来,肯定是行不通的,所以需要这个类    @Autowired    private AutowireCapableBeanFactory capableBeanFactory;    @Override    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {        //调用父类的方法        Object jobInstance = super.createJobInstance(bundle);        //进行注入        capableBeanFactory.autowireBean(jobInstance);        return jobInstance;    }}

任务类

@DisallowConcurrentExecution //作业不并发@Componentpublic class HelloWorldJob implements Job{    @Override    public void execute(JobExecutionContext arg0) throws JobExecutionException {        System.out.println("欢迎使用yyblog,这是一个定时任务  --小卖铺的老爷爷!"+ DateUtils.fullTime(new Date()));    }}

好了,大功告成,一个简单的动态配置的定时任务已经完成。是不是so easy,下面我们再来简单实现下其他的几种常用的api吧。

暂停一个job

    public void pauseJob(TaskDO task) throws SchedulerException {        JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());        scheduler.pauseJob(jobKey);    }

恢复一个job

    public void resumeJob(TaskDO task) throws SchedulerException {        JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());        scheduler.resumeJob(jobKey);    }

删除一个job

    public void deleteJob(TaskDO task) throws SchedulerException {        JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());        scheduler.deleteJob(jobKey);    }

立即触发job

    public void runJobNow(TaskDO task) throws SchedulerException {        JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());        scheduler.triggerJob(jobKey);    }

更新job表达式

    public void updateJobCron(TaskDO task) throws SchedulerException {        TriggerKey triggerKey = TriggerKey.triggerKey(task.getJobName(), task.getJobGroup());        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCronExpression());        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();        scheduler.rescheduleJob(triggerKey, trigger);    }

彩蛋部分~

好了,正文部分基本上就这些,不多废话了,本文没有太多的讲解原理,只是简单的应用,水平不够还望大家见谅。

看了上面的效果图是不是还是有种看的见摸不着的感觉,老爷爷这里也做了个线上的实例供大家体验~有兴趣的同学可以亲自试验一下。

Quartz文档地址:https://github.com/allanzhuo/yyblog/tree/master/doc

最后的最后,看都看了,码字不易,顺手点个赞白~^_^

源码地址

首先点击右下方在看,再长按下方二维码关注哦,并后台回复

p037】即可获得 GitHub 地址。

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

二维码

欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

已在知识星球更新源码解析如下:

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

SpringBoot 整合 Quartz 实现 JAVA 定时任务的动态配置

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。谢谢支持哟 (*^__^*)

阅读原文

继续阅读
weinxin
资源分享QQ群
本站是一个IT技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: