Quartz實現JAVA定時任務的動態配置

什麼是動態配置定時任務?

首先說下此次主題,動態配置。沒接觸過定時任務的同窗能夠先看下此篇:JAVA定時任務實現的幾種方式html

定時任務實現方式千人千種,不過基礎的無外乎 一、JDK 的Timer類 二、Quartz 三、SpringTask  。生產上三種方式我都有使用過。可是使用過程當中用的最多的即是xml配置的方式,這種方式最簡單,無代碼侵入,也比較好理解。
可是卻有個致命的缺點,好比你要改某個任務的觸發時間,亦或是你要新增一個任務,暫停一個任務。怎麼作?
停應用!改XML配置!從新啓動!
是否是很致命。最近從新研究了下Quartz的配置,實現了不停機添加、暫停、刪除、當即觸發任務的方法,在本篇分享出來,其實也不算是徹底的研究,在上家公司已經有大佬實現了,此次是也是基於大佬的理解從新實現下。git

國際慣例~先看效果圖   ps:文末有彩蛋哦~~

管理界面:github

 效果圖:實在不知道該跑什麼了,每隔十秒打一段話吧spring

技術實現

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監聽express

②讀取數據庫,將開啓的任務job和trigger加載到scheduler調度器api

③根據任務調度運行job類springboot

④每次運行利用AdaptableJobFactory實例化job類,以便注入要運行的service併發

聽着是否是很簡單,但卻仍是一頭霧水,且聽我慢慢道來~~app

 代碼邏輯

第一步:啓動項目,加載監聽

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.
接口被用做將其加入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,這裏僅介紹一些經常使用的,想要了解更多能夠稍後看下彩蛋部分。

一、start()方法:只有調用start()方法後,Scheduler線程纔開始啓動觸發器trigger,運行job

二、pauseJob(JobKey jobKey) :根據指定的JobDetail key暫停job。

三、resumeJob(JobKey jobKey) :根據指定的key恢復一個job。

四、deleteJob(JobKey jobKey) :刪除一個job

五、triggerJob(JobKey jobKey) :觸發一個JobDetail(如今執行)。

六、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

最後的最後,看都看了,碼字不易,順手點個贊白~^_^

相關文章
相關標籤/搜索