先說點無關本文的問題,這段時間特別的不爽,可能有些同學也遇到過。其實也可以說是小事一樁,但感覺也是不容忽視的。我剛畢業時的公司,每個人每次提交代碼都有著嚴格的規范,像table和space的縮進都有嚴格的要求,可以說你不遵守開發規范就相當于線上bug問題,還是比較嚴重的?,F在發現外面的公司真的是沒那么重視這個不重要卻又特別重要的問題啊,啊啊啊啊啊?。。?!
什么是動態配置定時任務?
回歸正題,說下這次主題,動態配置。沒接觸過定時任務的同學可以先看下此篇:JAVA定時任務實現的幾種方式
定時任務實現方式千人千種,不過基礎的無外乎 1、JDK 的Timer類 2、Quartz 3、SpringTask 。生產上三種方式我都有使用過。但是使用過程中用的最多的便是xml配置的方式,這種方式最簡單,無代碼侵入,也比較好理解。
但是卻有個致命的缺點,比如你要改某個任務的觸發時間,亦或是你要新增一個任務,暫停一個任務。怎么做?
停應用!改XML配置!重新啟動!
是不是很致命。最近重新研究了下Quartz的配置,實現了不停機添加、暫停、刪除、立即觸發任務的方法,在本篇分享出來,其實也不算是完全的研究,在上家公司已經有大佬實現了,這次是也是基于大佬的理解重新實現下。
國際慣例~先看效果圖 ps:文末有彩蛋哦~~
管理界面:

效果圖:實在不知道該跑什么了,每隔十秒打一段話吧
技術實現 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中配置
@Configuration
public 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.
第二步:讀取數據庫,加載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
工廠類
@Component
public 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 //作業不并發
@Component
public 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);
}
彩蛋部分~
好了,正文部分基本上就這些,不多廢話了,本文沒有太多的講解原理,只是簡單的應用,水平不夠還望大家見諒。
看了上面的效果圖是不是還是有種看的見摸不著的感覺,老爺爺這里也做了個線上的實例供大家體驗~有興趣的同學可以親自試驗一下。
實例地址: http://www.laoyeye.net/management/index 賬號/密碼:test/123456 菜單:系統設置/計劃任務
當然了還有最重要的源碼和API文檔我也不會忘的。
源碼地址: https://github.com/allanzhuo/yyblog
Quartz文檔地址: https://github.com/allanzhuo/yyblog/tree/master/doc
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。