1. 程式人生 > >SpringBoot整合quartz動態建立定時任務實現以及踩過的坑

SpringBoot整合quartz動態建立定時任務實現以及踩過的坑

需要了解一下quartz有Job,Scheduler,Trigger等概念,在此就不詳細說明

首先說一下功能需求場景
該提醒可以在頁面上進行配置多個,比如可以配置一個提醒:在每天的12點,傳送郵件給某個人,可以繼續配置另外一個提醒:在每個月的10號,給某個人轉賬,等等,多個動態的提醒,

說一下實現的方式
上面的需求即通過頁面的配置,創建出來對應的定時任務,這些任務是動態創建出來的,不能夠在程式碼中固定,該本章採用了quartz的Scheduler任務排程來實現動態任務的建立

實現功能路上踩過的坑
最開始的時候,對於這個動態建立定時任務我也覺得一臉懵逼,於是就在網上查詢資料
最開始採用

https://blog.csdn.net/upxiaofeng/article/details/79415108 這篇文章的思路進行編碼,最開始測試是可以的,專案啟動,成功創建出來對應的定時任務,但是在定時任務到達執行時間的時候,在執行繼承Job介面的類中,發現通過Autowired注入進來的物件為null,通過查資料一時也沒有解決,專案使用的springboot,網上的都是通過xml進行配置,於是就放棄了這篇文章的實現,之後又找到另一篇文章https://blog.csdn.net/lyg_come_on/article/details/78223344,一路照著敲完,發現在

StdScheduler stdScheduler = (StdScheduler) annotationContext.getBean(“mySchedulerFactoryBean”);//獲得上面建立的bean

的時候報錯了,原來是專案啟動的時候,先進行建立定時任務,但是此刻並沒有初始化MyJobFactory類中的bean,導致在建立scheduler時候並沒有在spring上下文中找到這個bean,於是就打算寫一個定時器,然後每天執行一次,執行的時候,呼叫動態建立定時任務的方法,剛好這樣做,可以實現,在頁面上修改任務提醒的配置,後臺不用重新啟動就可以動態更新任務,現在要實現就是對動態創建出來的任務進行修改定時時間,新增定時任務的操作了.

實現

1. InitRemindRuleScheduler
2. JobFactory
3. RemindTask
4. RemindRuleScheduler
5. 
TimingRemindTask
  1. InitRemindRuleScheduler 該本來用作建立mySchedulerFactoryBean的bean和呼叫RemindRuleScheduler的initStartJob來在專案啟動的時候建立定時任務,無奈出現上面的問題,就只用來建立mySchedulerFactoryBean這個bean物件了
  2. JobFactory 用來建立job例項
  3. RemindTask建立的一個定時任務,每天會執行一遍,代替1中的onApplicationEvent方法
  4. RemindRuleScheduler從資料庫查詢定時提醒任務,進行更新任務.
  5. TimingRemindTask 任務的具體操作

說明一下,因為目前我的多個定期任務都是同一個任務邏輯操作,故只需要一個定時任務的業務邏輯實現,如果針對不同的任務,有不同的實現的話,在建立定時任務的時候,要針對生成這個任務的時候使用對應的類:TimingRemindTask(該類需實現Job介面)

JobDetail jobDetail = JobBuilder.newJob(TimingRemindTask.class)
                .withIdentity(remindRuleObjF04SQL01OM01.getId(), REMINDRULE )
                .usingJobData("remindRuleId",remindRuleObjF04SQL01OM01.getId())
                .build();

最後,貼上程式碼:
1. InitRemindRuleScheduler.class

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import com.paasit.pai.core.quartz.RemindRuleScheduler;

/**
 * @author 和彥鵬
 * 2018年9月15日
 */
@Configuration
public class InitRemindRuleScheduler implements ApplicationListener<ContextRefreshedEvent> {

    /**
     * 日誌
     */
    private final Logger log = LoggerFactory.getLogger(InitRemindRuleScheduler.class);

    @Autowired
    private RemindRuleScheduler remindRuleScheduler;

    @Autowired
    private JobFactory jobFactory;

    @Bean(name ="mySchedulerFactoryBean")
    public SchedulerFactoryBean mySchedulerFactory() {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setOverwriteExistingJobs(true);
        bean.setStartupDelay(1);
        bean.setJobFactory(jobFactory);
        return bean;
    }

    /**
     * 專案初始化的時候啟動quartz
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //log.debug("執行onApplicationEvent..");
        //try {
        //    remindRuleScheduler.initStartJob();
        //    log.debug("任務已經啟動...");
        //} catch (SchedulerException e) {
        //    log.error("初始化啟動錯誤:{}", e);
        //}
    }

}
  1. JobFactory
import org.quartz.spi.TriggerFiredBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

/**
 * @author 和彥鵬
 * 2018年9月16日
 */
@Component
public class JobFactory extends AdaptableJobFactory {

    private static final Logger log = LoggerFactory.getLogger(JobFactory.class);

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 呼叫父類的方法
        Object jobInstance = super.createJobInstance(bundle);
        // 進行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}
  1. RemindTask
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * @version: 0_1
 * @author: 和彥鵬
 * @date: 2018年6月6日11:37:44
 */
@Component
@Configurable
@EnableScheduling
public class RemindTask {

    /**
     * 日誌
     */
    private final Logger log = LoggerFactory.getLogger(RemindTask.class);

    /**
     * 初始化動態建立定期任務
     */
    @Autowired
    private RemindRuleScheduler remindRuleScheduler;

    /**
     * 執行間隔提醒業務邏輯
     */
    @Autowired
    private IntervalRemindTask intervalRemindTask;

    // 每天執行定時任務:測試使用,每分鐘執行
    @Scheduled(cron = "5 * * * * ?")
    public void testGetDemoData() {
        log.debug("RemindTask starting...");
        // A:建立或者更新定期的所有動態任務
        try {
            remindRuleScheduler.initStartJob();
        } catch (SchedulerException e) {
            log.error("RemindTask 初始化定期提醒任務失敗.. {}", e);
        }
        log.debug("RemindTask end...");
    }
}
  1. RemindRuleScheduler
import java.text.MessageFormat;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdScheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.paasit.pai.core.dao.QueryDAO;
import com.paasit.pai.core.sql.dto.remindRuleObj.RemindRuleObjF04SQL01IM01;
import com.paasit.pai.core.sql.dto.remindRuleObj.RemindRuleObjF04SQL01OM01;
import com.paasit.pai.core.utils.SpringUtil;

/**
 * 描述: 提醒規則任務:
 * @author 和彥鵬
 * @date 2018年9月15日15:33:02
 * @version v0.0.1
 */
@Component
public class RemindRuleScheduler {

    /**
     * 日誌
     */
    private final Logger log = LoggerFactory.getLogger(RemindRuleScheduler.class);

    private final String REMINDRULE = "REMINDRULE";

    @Autowired
    private QueryDAO queryDAO;


    /**
     * 開始執行所有任務
     * 
     * @throws SchedulerException
     */
    public void initStartJob() throws SchedulerException {
        // 從資料庫中查詢到所有的提醒
        ApplicationContext annotationContext = SpringUtil.getApplicationContext();
        StdScheduler stdScheduler = (StdScheduler) annotationContext.getBean("mySchedulerFactoryBean");//獲得上面建立的bean
        Scheduler myScheduler = stdScheduler;
        RemindRuleObjF04SQL01IM01 remindRuleObjF04SQL01IM01 = new RemindRuleObjF04SQL01IM01();
        remindRuleObjF04SQL01IM01.setRemindType(0);// 0表示定期,該方法僅執行定期
        List<RemindRuleObjF04SQL01OM01> remindRuleObjF04SQL01OM01List = queryDAO.executeForObjectList("RemindRuleObjF04SQL01", remindRuleObjF04SQL01IM01);
        log.debug("RemindTask 資料庫查詢出來的定期任務數量:{}", remindRuleObjF04SQL01OM01List.size());
        if (remindRuleObjF04SQL01OM01List != null && remindRuleObjF04SQL01OM01List.size() > 0) {
            for (int i = 0; i < remindRuleObjF04SQL01OM01List.size(); i++) {
                startJob(myScheduler,remindRuleObjF04SQL01OM01List.get(i));
            }
            myScheduler.start();
        }
    }

    private void startJob(Scheduler myScheduler, RemindRuleObjF04SQL01OM01 remindRuleObjF04SQL01OM01) throws SchedulerException {
        // 建立觸發器表示式{0}:表示小時,{1}:表示日, 
        String cron = MessageFormat.format("0 0 {0} {1} * ?", 
                remindRuleObjF04SQL01OM01.getOnHour() == null ? 0 : remindRuleObjF04SQL01OM01.getOnHour(),
                        remindRuleObjF04SQL01OM01.getOnDay() == null || remindRuleObjF04SQL01OM01.getOnDay() == 0 ? "*" : remindRuleObjF04SQL01OM01.getOnDay());
        log.info(MessageFormat.format("RemindTask 提醒任務Id=[{0}]的cron的表示式:[{1}]", remindRuleObjF04SQL01OM01.getId(), cron));

        TriggerKey triggerKey = new TriggerKey(remindRuleObjF04SQL01OM01.getId(), REMINDRULE);  
        CronTrigger cronTrigger = (CronTrigger) myScheduler.getTrigger(triggerKey);
        // 不存在這個任務,新增任務
        if (cronTrigger == null) {
            // 通過JobBuilder構建JobDetail例項,JobDetail規定只能是實現Job介面的例項
            JobDetail jobDetail = JobBuilder.newJob(TimingRemindTask.class)
                    .withIdentity(remindRuleObjF04SQL01OM01.getId(), REMINDRULE)
                    .usingJobData("remindRuleId",remindRuleObjF04SQL01OM01.getId())
                    .build();
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
            // CronTrigger表示式觸發器 繼承於Trigger
            cronTrigger = TriggerBuilder.newTrigger().withIdentity(remindRuleObjF04SQL01OM01.getId(), REMINDRULE)
                    .withSchedule(cronScheduleBuilder).build();
            myScheduler.scheduleJob(jobDetail, cronTrigger);
            log.info("RemindTask 建立定時任務成功");
        } else {// 存在這個任務,判斷這個任務的觸發時間是否被修改過,如果修改過則更新任務
            String oldCron = cronTrigger.getCronExpression();
            // 新配置的cron和之前任務中使用的不一致,則更新
            if (!StringUtils.equalsIgnoreCase(cron, oldCron)) {
                CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
                CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(remindRuleObjF04SQL01OM01.getId(), REMINDRULE)
                        .withSchedule(cronScheduleBuilder).build();
                myScheduler.rescheduleJob(triggerKey, trigger);
                log.info("RemindTask 更新任務執行時間成功");
            } else {
                log.info("RemindTask 任務不進行操作");
            }
        } // TODO 暫沒考慮頁面上刪除定期任務提醒
    }


//    /**
//     * 獲取Job資訊
//     */
//    public String getJobInfo(String name, String group) throws SchedulerException {
//        TriggerKey triggerKey = new TriggerKey(name, group);
//        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//        return String.format("time:%s,state:%s", cronTrigger.getCronExpression(),
//                scheduler.getTriggerState(triggerKey).name());
//    }
//    
//    /**
//     * 修改某個任務的執行時間
//     */
//    public boolean modifyJob(String name, String group, String time) throws SchedulerException {
//        Date date = null;
//        TriggerKey triggerKey = new TriggerKey(name, group);
//        CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//        String oldTime = cronTrigger.getCronExpression();
//        if (!oldTime.equalsIgnoreCase(time)) {
//            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time);
//            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group)
//                    .withSchedule(cronScheduleBuilder).build();
//            date = scheduler.rescheduleJob(triggerKey, trigger);
//        }
//        return date != null;
//    }
//    
//    /**
//     * 暫停所有任務
//     */
//    public void pauseAllJob() throws SchedulerException {
//        scheduler.pauseAll();
//    }
//    
//    /**
//     * 暫停某個任務
//     */
//    public void pauseJob(String name, String group) throws SchedulerException {
//        JobKey jobKey = new JobKey(name, group);
//        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
//        if (jobDetail == null)
//            return;
//        scheduler.pauseJob(jobKey);
//    }
//
//    /**
//     * 恢復所有任務
//     */
//    public void resumeAllJob() throws SchedulerException {
//        scheduler.resumeAll();
//    }
//
//    /**
//     * 恢復某個任務
//     */
//    public void resumeJob(String name, String group) throws SchedulerException {
//        JobKey jobKey = new JobKey(name, group);
//        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
//        if (jobDetail == null)
//            return;
//        scheduler.resumeJob(jobKey);
//    }
//
//    /**
//     * 刪除某個任務
//     */
//    public void deleteJob(String name, String group) throws SchedulerException {
//        JobKey jobKey = new JobKey(name, group);
//        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
//        if (jobDetail == null)
//            return;
//        scheduler.deleteJob(jobKey);
//    }

}
  1. TimingRemindTask
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.paasit.pai.core.dao.QueryDAO;
import com.paasit.pai.core.sql.dto.remindRuleObj.RemindRuleObjF04SQL01IM01;
import com.paasit.pai.core.sql.dto.remindRuleObj.RemindRuleObjF04SQL01OM01;

/**
 * 定時提醒的任務邏輯:該方法為動態創建出來的定時任務執行的邏輯,精確到remindRuleId,僅執行該條提醒的配置
 * @author 和彥鵬
 * @date 2018年9月16日15:33:09
 * @version v0.1
 */
@Component
public class TimingRemindTask implements Job {

    /**
     * 日誌
     */
    private final Logger log = LoggerFactory.getLogger(TimingRemindTask.class);

    @Autowired
    private QueryDAO queryDAO;


    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        try {
            String remindRuleId = arg0.getJobDetail().getJobDataMap().getString("remindRuleId");
            log.debug("RemindTask 1.1執行定期提醒任務資訊Id:{}", remindRuleId);
            RemindRuleObjF04SQL01IM01 remindRuleObjF04SQL01IM01 = new RemindRuleObjF04SQL01IM01();
            remindRuleObjF04SQL01IM01.setId(remindRuleId);
            RemindRuleObjF04SQL01OM01 remindRuleObjF04SQL01OM01 = queryDAO.executeForObject("RemindRuleObjF04SQL01", remindRuleObjF04SQL01IM01, RemindRuleObjF04SQL01OM01.class);
            // 進行業務邏輯處理
        } catch (Exception e) {
            log.error("RemindTask 1.9執行任務,獲取任務資訊Id出錯,任務資訊:{}", arg0);
        }
    }
}