Spring Boot 整合 Quartz 实现 Java 定时任务的动态配置

沙海
沙海
沙海
1016
文章
2
评论
2021年4月16日12:09:33
评论
3 7439字阅读24分47秒
摘要

Spring Boot 整合 Quartz 实现 Java 定时任务的动态配置 搜云库技术团队

Spring Boot 整合 Quartz 实现 Java 定时任务的动态配置

搜云库技术团队

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

首先说下这次主题,动态配置。定时任务实现方式千人千种,不过基础的无外乎

1、JDK 的Timer类2、Quartz3、SpringTask

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

注 意

文末有:3625页互联网大厂面试题

国际惯例~先看效果图 

管理界面:

Spring Boot 整合 Quartz 实现 Java 定时任务的动态配置

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

Spring Boot 整合 Quartz 实现 Java 定时任务的动态配置

技术实现

maven依赖

使用springboot做框架支持

<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中配置

@ConfigurationpublicclassQuartzConfigration{    @Autowired    privateJobFactory jobFactory;    @Bean    publicSchedulerFactoryBean schedulerFactoryBean() {        SchedulerFactoryBean schedulerFactoryBean = newSchedulerFactoryBean();        try{            schedulerFactoryBean.setOverwriteExistingJobs(true);            schedulerFactoryBean.setQuartzProperties(quartzProperties());            schedulerFactoryBean.setJobFactory(jobFactory);        } catch(Exception e) {            e.printStackTrace();        }        return schedulerFactoryBean;    }    // 指定quartz.properties,可在配置文件中配置相关属性    @Bean    publicProperties quartzProperties() throwsIOException{        PropertiesFactoryBean propertiesFactoryBean = newPropertiesFactoryBean();        propertiesFactoryBean.setLocation(newClassPathResource("/config/quartz.properties"));        propertiesFactoryBean.afterPropertiesSet();        return propertiesFactoryBean.getObject();    }    // 创建schedule    @Bean(name = "scheduler")    publicScheduler scheduler() {        return schedulerFactoryBean().getScheduler();    }}

监听器

@Component@Order(value = 1)publicclassScheduleJobInitListenerimplementsCommandLineRunner{    @Autowired    TaskService scheduleJobService;    @Override    publicvoid run(String... arg0) throwsException{        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方法

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

添加任务到Quartz调度器

/** * 添加任务*/    @SuppressWarnings("unchecked")publicvoid addJob(TaskDO task) {    try{        // 创建jobDetail实例,绑定Job实现类        // 指明job的名称,所在组的名称,以及绑定job类        Class<? extendsJob> jobClass = (Class<? extendsJob>) (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

工厂类

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

任务类

@DisallowConcurrentExecution//作业不并发@ComponentpublicclassHelloWorldJobimplementsJob{    @Override    publicvoid execute(JobExecutionContext arg0) throwsJobExecutionException{        System.out.println("欢迎使用yyblog,这是一个定时任务  --小卖铺的老爷爷!"+ DateUtils.fullTime(newDate()));    }}

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

暂停一个job

publicvoid pauseJob(TaskDO task) throwsSchedulerException{    JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());    scheduler.pauseJob(jobKey);}

恢复一个job

    publicvoid resumeJob(TaskDO task) throwsSchedulerException{        JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());        scheduler.resumeJob(jobKey);    }

删除一个job

publicvoid deleteJob(TaskDO task) throwsSchedulerException{    JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());    scheduler.deleteJob(jobKey);}

立即触发job

publicvoid runJobNow(TaskDO task) throwsSchedulerException{    JobKey jobKey = JobKey.jobKey(task.getJobName(), task.getJobGroup());    scheduler.triggerJob(jobKey);}

更新job表达式

publicvoid updateJobCron(TaskDO task) throwsSchedulerException{    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);}

作者:小卖铺的老爷爷

链接:cnblogs.com/laoyeye/p/9352002.html

最后

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

当然了还有最重要的源码和API文档我也不会忘的。

源码地址:

https://github.com/allanzhuo/yyblog

Quartz文档地址:

https://github.com/allanzhuo/yyblog/tree/master/doc

最后的最后,看都看了,码字不易,顺手点个再看呗~^

近期技术热文

1、什么 MySQL 默认值为 null,它不推荐?2、为什么大家都说SELECT * 效率低3、如何访问Redis中的海量数据?避免事故产生4、跟狗屎一样的代码,到底该如何重构?

第3版:互联网大厂面试题

包括 Java 集合、JVM、多线程、并发编程、设计模式、算法调优、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、Python、HTML、CSS、Vue、React、JavaScript、Android 大数据、阿里巴巴等大厂面试题等、等技术栈!

阅读原文: 高清 7701页大厂面试题  PDF

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

发表评论

匿名网友 填写信息

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