SpringBoot 整合 Quartz 實現依賴資料庫資料動態設定定時任務

進階的小碼農發表於2018-01-31

簡單的定時任務用 spring的 @Scheduled 註解即可

@Component
public class ScheduledTask {
    
    @Scheduled(fixedRate = 5000)
    //表示每隔5000ms,Spring scheduling會呼叫一次該方法,不論該方法的執行時間是多少
    public void reportCurrentTime() throws InterruptedException {
        System.out.println(new Date()));
    }

    @Scheduled(fixedDelay = 5000)
    //表示當方法執行完畢5000ms後,Spring scheduling會再次呼叫該方法
    public void reportCurrentTimeAfterSleep() throws InterruptedException {
        System.out.println(new Date()));
    }

    @Scheduled(cron = "0 0 1 * * *")
    //提供了一種通用的定時任務表示式,這裡表示每隔5秒執行一次,更加詳細的資訊可以參考cron表示式。
    public void reportCurrentTimeCron() throws InterruptedException {
        System.out.println(new Date()));
    }
}
複製程式碼
  • 執行類
@SpringBootApplication
@EnableScheduling
//告訴Spring建立一個task executor,如果我們沒有這個標註,所有@Scheduled標註都不會執行
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

複製程式碼

Quartz 實現動態設定定時任務

  • 官網:http://www.quartz-scheduler.org/
  • 實戰

首先需要一個配置類

package com.*.myquartz;
import org.quartz.Scheduler;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

/**
 * @Author by xup .
 * @Descriptions
 * @Datetime in 2018/1/30 17:07.
 */
@Configuration
public class QuartzConfiguration {
    //解決Job中注入Spring Bean為null的問題
    @Component("quartzJobFactory")
    private class QuartzJobFactory extends AdaptableJobFactory {
        //這個物件Spring會幫我們自動注入進來,也屬於Spring技術範疇.
        @Autowired
        private AutowireCapableBeanFactory capableBeanFactory;

        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
            //呼叫父類的方法
            Object jobInstance = super.createJobInstance(bundle);
            //進行注入,這屬於Spring的技術,不清楚的可以檢視Spring的API.
            capableBeanFactory.autowireBean(jobInstance);
            return jobInstance;
        }
    }
    //注入scheduler到spring,在下面quartzManege會用到
    @Bean(name = "scheduler")
    public Scheduler scheduler(QuartzJobFactory quartzJobFactory) throws Exception {

        SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
        factoryBean.setJobFactory(quartzJobFactory);
        factoryBean.afterPropertiesSet();
        Scheduler scheduler=factoryBean.getScheduler();
        scheduler.start();
        return scheduler;
    }
}

複製程式碼

還需要quartz 用到的實體類

package com.*.myquartz;
import lombok.Data;

/**
  * @author xup
  * @since 2018-01-29
 */
@Data
public class QuartzJob {

	public static final Integer STATUS_RUNNING = 1;
	public static final Integer STATUS_NOT_RUNNING = 0;
	public static final Integer CONCURRENT_IS = 1;
	public static final Integer CONCURRENT_NOT = 0;

	private String jobId;
        /**
         * cron 表示式
         */
	private String cronExpression;
        /**
         * 任務呼叫的方法名
         */
	private String methodName;
        /**
         * 任務是否有狀態
         */
	private Integer isConcurrent;
        /**
         * 描述
         */
	private String description;
        /**
         * 任務執行時呼叫哪個類的方法 包名+類名,完全限定名
         */
	private String beanName;
    	/**
         * 觸發器名稱
         */
	private String triggerName;

        /**
         * 任務狀態
         */
	private Integer jobStatus;
	private String springBean;
        /**
         * 任務名
         */
	private String jobName;

}

複製程式碼

接著是任務管理類

根據傳入quartzJob 類引數決定呼叫那個類的定時任務方法 與上面介紹的簡單schedule更靈活。可實現不修改配置檔案來開啟和停用定時任務,無需重啟專案

package com.*.myquartz;
import org.quartz.*;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * @Author by xup .
 * @Descriptions
 * @Datetime in 2018/1/30 17:20.
 */
@Component
public class QuartzManage {

    @Resource(name = "scheduler")
    private Scheduler scheduler;

    public void addJob(QuartzJob job) throws SchedulerException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        //通過類名獲取實體類,即要執行的定時任務的類
        Class<?> clazz = Class.forName(job.getBeanName());
        Job jobEntity = (Job)clazz.newInstance();
        //通過實體類和任務名建立 JobDetail
        JobDetail jobDetail = newJob(jobEntity.getClass())
                .withIdentity(job.getJobName()).build();
        //通過觸發器名和cron 表示式建立 Trigger
        Trigger cronTrigger = newTrigger()
                .withIdentity(job.getTriggerName())
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
                .build();
        //執行定時任務
        scheduler.scheduleJob(jobDetail,cronTrigger);
    }

    /**
     * 更新job cron表示式
     * @param quartzJob
     * @throws SchedulerException
     */
    public void updateJobCron(QuartzJob quartzJob) throws SchedulerException {

        TriggerKey triggerKey = TriggerKey.triggerKey(quartzJob.getJobName());
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression());
        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
        scheduler.rescheduleJob(triggerKey, trigger);
    }
    /**
     * 刪除一個job
     * @param quartzJob
     * @throws SchedulerException
     */
    public void deleteJob(QuartzJob quartzJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(quartzJob.getJobName());
        scheduler.deleteJob(jobKey);
    }
    /**
     * 恢復一個job
     * @param quartzJob
     * @throws SchedulerException
     */
    public void resumeJob(QuartzJob quartzJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(quartzJob.getJobName());
        scheduler.resumeJob(jobKey);
    }
    /**
     * 立即執行job
     * @param quartzJob
     * @throws SchedulerException
     */
    public void runAJobNow(QuartzJob quartzJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(quartzJob.getJobName());
        scheduler.triggerJob(jobKey);
    }
    /**
     * 暫停一個job
     * @param quartzJob
     * @throws SchedulerException
     */
    public void pauseJob(QuartzJob quartzJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(quartzJob.getJobName());
        scheduler.pauseJob(jobKey);
    }
}

複製程式碼

最後是測試類

注意:定時任務必須實現Job介面, 然後將定時任務的方法放入execute()方法中即可

package com.*.myquartz;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;

@Slf4j
public class TaskTest implements Job {

	public void run() {
	    for (int i = 0; i < 10; i++) {
    		log.info(i+" run ################## " + (new Date()));
    	    }
	}

	@Override
	public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
	    run();
	}
}

複製程式碼

後臺管理介面,可隨時開啟,停用定時任務

SpringBoot 整合 Quartz 實現依賴資料庫資料動態設定定時任務

接入業務邏輯實現資料庫同步

這裡的實體類(針對資料庫)和quartz中的實體類(針對quartz)需要做一下轉換,當然他們的欄位可以完全一致

    @Autowired
    private QuartzManage quartzManage;

    @Autowired
    private ScheduleJobDao scheduleJobDao;
    
    //這裡新增定時任務時,預設是不開啟的,需要手動執行開啟,暫停任務
    @Override
    public ResultData<Boolean> addScheduleJob(ScheduleJobForm scheduleJobForm) {
        if (!CronExpression.isValidExpression(scheduleJobForm.getCronExpression())){
            return new ResultData<>(ResultMsg.ERROR,"cron表示式格式錯誤!");
        }
        return new ResultData<>(add(scheduleJobForm));
    }

    //開啟或暫停定時任務
    @Override
    public boolean changeJobStatus(ScheduleJobForm scheduleJobForm) throws SchedulerException, IllegalAccessException, InstantiationException, ClassNotFoundException {
        ScheduleJobForm oldJob = scheduleJobDao.selectById(scheduleJobForm.getJobId());
        oldJob.setJobStatus(scheduleJobForm.getJobStatus());
        if (XpObjectUtil.equals(scheduleJobForm.getJobStatus(), QuartzJob.STATUS_RUNNING)){
            quartzManage.addJob(QuartzJobUtils.entityToData(oldJob));
        } else if (XpObjectUtil.equals(scheduleJobForm.getJobStatus(), QuartzJob.STATUS_NOT_RUNNING)) {
            quartzManage.deleteJob(QuartzJobUtils.entityToData(oldJob));
        }
        scheduleJobForm.setUpdateBy(SecurityUtil.getCurrUserAccount());
        return editById(scheduleJobForm);
    }

複製程式碼
  • 這裡還有個問題,專案重啟時所有執行的定時任務會被銷燬

下面的操作即為專案啟動時重新載入啟用狀態的定時任務

這時實現 ApplicationRunner介面 重寫run方法即可 專案啟動時會執行run方法裡的操作

package com.*.myquartz;
import com.*.enums.DataStatus;
import com.*.task.schedule.form.ScheduleJobForm;
import com.*.task.schedule.service.ScheduleJobService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @Author by xup .
 * @Descriptions
 * @Datetime in 2018/1/31 11:41.
 */

@Component
@Slf4j
public class MyApplicationRunner implements ApplicationRunner {
    @Autowired
    private ScheduleJobService scheduleJobService;

    @Override
    //專案啟動時重新啟用啟用的定時任務
    public void run(ApplicationArguments applicationArguments) throws Exception {
        ScheduleJobForm scheduleJobForm = new ScheduleJobForm();
        scheduleJobForm.setDeleteFlag(DataStatus.DEFAULT.getStatus());
        scheduleJobForm.setJobStatus(1);
        List<ScheduleJobForm> scheduleJobList = scheduleJobService.selectList(scheduleJobForm);
        log.info("##########################"+scheduleJobList.size());
        if (CollectionUtils.isNotEmpty(scheduleJobList)){
            scheduleJobService.initScheduleJob(scheduleJobList);
        }
    }
}

複製程式碼

到此一個完整的定時任務管理模組完成了

相關文章