springboot + Quartz 實現(xiàn)任務(wù)調(diào)度

在spring框架中通過@Schedule可以實現(xiàn)定時任務(wù)争涌,通過該注解cron的屬性描述的規(guī)則损俭,spring會去調(diào)用這個方法痹栖。
spring已經(jīng)簡單粗暴的實現(xiàn)了定時任務(wù)亿汞,為什么要使用Quartz ?
如果你現(xiàn)在有很多個定時任務(wù)揪阿,規(guī)則不同疗我,例如:

  • 想每月25號,信用卡自動還款
  • 想每年4月1日自己給當(dāng)年暗戀女神發(fā)一封匿名賀卡
  • 想每隔1小時图甜,備份一下自己的愛情動作片 學(xué)習(xí)筆記到云盤

maven 依賴

  <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz</artifactId>
      <version>2.2.1</version>
  </dependency>
  <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz-jobs</artifactId>
      <version>2.2.1</version>
  </dependency> 

以下是bootdo開源學(xué)習(xí)框架的源碼
思路是先查詢DB,封裝以下兩個對象

  • JobDetail 負責(zé)存放 job 所需要的數(shù)據(jù)
  • Trigger 設(shè)置 job 的 key鳖眼,規(guī)則(cron)何時開啟任務(wù)等屬性
    當(dāng)觸發(fā)條件滿足時黑毅,會根據(jù)所設(shè)置的beanClass 找到該類(必須實現(xiàn)org.quartz.Job),
    這時可以取出JobDetail 中的數(shù)據(jù)钦讳,執(zhí)行具體業(yè)務(wù)邏輯
@Component
public class WelcomeJob implements Job{
    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        //你的業(yè)務(wù)邏輯
    }

}

表結(jié)構(gòu)矿瘦,我并沒有用到全部的字段

CREATE TABLE `sys_task` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `cronExpression` varchar(255) DEFAULT NULL COMMENT 'cron表達式',
  `methodName` varchar(255) DEFAULT NULL COMMENT '任務(wù)調(diào)用的方法名',
  `isConcurrent` varchar(255) DEFAULT NULL COMMENT '任務(wù)是否有狀態(tài)',
  `description` varchar(255) DEFAULT NULL COMMENT '任務(wù)描述',
  `updateBy` varchar(64) DEFAULT NULL COMMENT '更新者',
  `beanClass` varchar(255) DEFAULT NULL COMMENT '任務(wù)執(zhí)行時調(diào)用哪個類的方法 包名+類名',
  `createDate` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
  `jobStatus` varchar(255) DEFAULT NULL COMMENT '任務(wù)狀態(tài)',
  `jobGroup` varchar(255) DEFAULT NULL COMMENT '任務(wù)分組',
  `updateDate` datetime DEFAULT NULL COMMENT '更新時間',
  `createBy` varchar(64) DEFAULT NULL COMMENT '創(chuàng)建者',
  `springBean` varchar(255) DEFAULT NULL COMMENT 'Spring bean',
  `jobName` varchar(255) DEFAULT NULL COMMENT '任務(wù)名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;


配置類

import java.io.IOException;
import java.util.Properties;

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import com.txgl.common.quartz.factory.JobFactory;

@Configuration
public class QuartzConfigration {

    @Autowired
    JobFactory jobFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        try {
            schedulerFactoryBean.setOverwriteExistingJobs(true);
            schedulerFactoryBean.setQuartzProperties(quartzProperties());
            schedulerFactoryBean.setJobFactory(jobFactory);
        } catch (IOException 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();
    }

    // 創(chuàng)建schedule
    @Bean(name = "scheduler")
    public Scheduler scheduler() {
        return schedulerFactoryBean().getScheduler();
    }
}

QuartzManager的代碼是關(guān)鍵,通過注入Scheduler 對任務(wù)進行操作

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.DateBuilder;
import org.quartz.DateBuilder.IntervalUnit;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import com.bootdo.common.domain.ScheduleJob;
import com.bootdo.common.quartz.factory.*;
import com.bootdo.common.utils.SpringContextHolder;;

/**
 * 
 * 
 * @title: QuartzManager.java
 * @description: 計劃任務(wù)管理
 *
 */
@Service
public class QuartzManager {
    public final Logger log = Logger.getLogger(this.getClass());
    // private SchedulerFactoryBean schedulerFactoryBean
    // =SpringContextHolder.getBean(SchedulerFactoryBean.class);
    // @Autowired
    // @Qualifier("schedulerFactoryBean")
    // private SchedulerFactoryBean schedulerFactoryBean;
    @Autowired
    private Scheduler scheduler;

    /**
     * 添加任務(wù)
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    
    public void addJob(ScheduleJob job) {
        try {
            // 創(chuàng)建jobDetail實例愿卒,綁定Job實現(xiàn)類
            // 指明job的名稱缚去,所在組的名稱,以及綁定job類
            Class<? extends Job> jobClass = (Class<? extends Job>) (Class.forName(job.getBeanClass()).newInstance()
                    .getClass());
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(job.getJobName(), job.getJobGroup())// 任務(wù)名稱和組構(gòu)成任務(wù)key
                    .build();
            // 定義調(diào)度觸發(fā)規(guī)則
            // 使用cornTrigger規(guī)則
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup())// 觸發(fā)器key
                    .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
                    .withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression())).startNow().build();
            // 把作業(yè)和觸發(fā)器注冊到任務(wù)調(diào)度中
            scheduler.scheduleJob(jobDetail, trigger);
            // 啟動
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
//  public void addJob(ScheduleJob job) throws SchedulerException {
//      if (job == null || !ScheduleJob.STATUS_RUNNING.equals(job.getJobStatus())) {
//          return;
//      }
//
//      TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
//
//      CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//
//      // 不存在琼开,創(chuàng)建一個
//
//      if (null == trigger) {
//          Class<? extends Job> clazz = ScheduleJob.CONCURRENT_IS.equals(job.getIsConcurrent())
//                  ? QuartzJobFactory.class
//                  : QuartzJobFactoryDisallowConcurrentExecution.class;
//
//          JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(job.getJobName(), job.getJobGroup()).build();
//
//          jobDetail.getJobDataMap().put("scheduleJob", job);
//
//          CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
//
//          trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup())
//                  .withSchedule(scheduleBuilder).build();
//
//          scheduler.scheduleJob(jobDetail, trigger);
//      } else {
//          // Trigger已存在易结,那么更新相應(yīng)的定時設(shè)置
//
//          CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
//
//          // 按新的cronExpression表達式重新構(gòu)建trigger
//
//          trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//
//          // 按新的trigger重新設(shè)置job執(zhí)行
//
//          scheduler.rescheduleJob(triggerKey, trigger);
//      }
//  }

    /**
     * 獲取所有計劃中的任務(wù)列表
     * 
     * @return
     * @throws SchedulerException
     */
    public List<ScheduleJob> getAllJob() throws SchedulerException {
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
        for (JobKey jobKey : jobKeys) {
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            for (Trigger trigger : triggers) {
                ScheduleJob job = new ScheduleJob();
                job.setJobName(jobKey.getName());
                job.setJobGroup(jobKey.getGroup());
                job.setDescription("觸發(fā)器:" + trigger.getKey());
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                job.setJobStatus(triggerState.name());
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cronExpression = cronTrigger.getCronExpression();
                    job.setCronExpression(cronExpression);
                }
                jobList.add(job);
            }
        }
        return jobList;
    }

    /**
     * 所有正在運行的job
     * 
     * @return
     * @throws SchedulerException
     */
    public List<ScheduleJob> getRunningJob() throws SchedulerException {
        List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());
        for (JobExecutionContext executingJob : executingJobs) {
            ScheduleJob job = new ScheduleJob();
            JobDetail jobDetail = executingJob.getJobDetail();
            JobKey jobKey = jobDetail.getKey();
            Trigger trigger = executingJob.getTrigger();
            job.setJobName(jobKey.getName());
            job.setJobGroup(jobKey.getGroup());
            job.setDescription("觸發(fā)器:" + trigger.getKey());
            Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
            job.setJobStatus(triggerState.name());
            if (trigger instanceof CronTrigger) {
                CronTrigger cronTrigger = (CronTrigger) trigger;
                String cronExpression = cronTrigger.getCronExpression();
                job.setCronExpression(cronExpression);
            }
            jobList.add(job);
        }
        return jobList;
    }

    /**
     * 暫停一個job
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢復(fù)一個job
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.resumeJob(jobKey);
    }

    /**
     * 刪除一個job
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.deleteJob(jobKey);

    }

    /**
     * 立即執(zhí)行job
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void runAJobNow(ScheduleJob scheduleJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.triggerJob(jobKey);
    }

    /**
     * 更新job時間表達式
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void updateJobCron(ScheduleJob scheduleJob) throws SchedulerException {

        TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());

        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());

        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

        scheduler.rescheduleJob(triggerKey, trigger);
    }
}

service實現(xiàn)

import com.bootdo.common.config.Constant;
import com.bootdo.common.dao.TaskDao;
import com.bootdo.common.domain.ScheduleJob;
import com.bootdo.common.domain.TaskDO;
import com.bootdo.common.quartz.utils.QuartzManager;
import com.bootdo.common.service.JobService;
import com.bootdo.common.utils.ScheduleJobUtils;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class JobServiceImpl implements JobService {

    @Autowired
    private TaskDao taskScheduleJobMapper;

    @Autowired
    QuartzManager quartzManager;

    @Override
    public TaskDO get(Long id) {
        return taskScheduleJobMapper.get(id);
    }

    @Override
    public List<TaskDO> list(Map<String, Object> map) {
        return taskScheduleJobMapper.list(map);
    }

    @Override
    public int count(Map<String, Object> map) {
        return taskScheduleJobMapper.count(map);
    }

    @Override
    public int save(TaskDO taskScheduleJob) {
        return taskScheduleJobMapper.save(taskScheduleJob);
    }

    @Override
    public int update(TaskDO taskScheduleJob) {
        return taskScheduleJobMapper.update(taskScheduleJob);
    }

    @Override
    public int remove(Long id) {
        try {
            TaskDO scheduleJob = get(id);
            quartzManager.deleteJob(ScheduleJobUtils.entityToData(scheduleJob));
            return taskScheduleJobMapper.remove(id);
        } catch (SchedulerException e) {
            e.printStackTrace();
            return 0;
        }

    }

    @Override
    public int batchRemove(Long[] ids) {
        for (Long id : ids) {
            try {
                TaskDO scheduleJob = get(id);
                quartzManager.deleteJob(ScheduleJobUtils.entityToData(scheduleJob));
            } catch (SchedulerException e) {
                e.printStackTrace();
                return 0;
            }
        }
        return taskScheduleJobMapper.batchRemove(ids);
    }

    @Override
    public void initSchedule() throws SchedulerException {
        // 這里獲取任務(wù)信息數(shù)據(jù)
        List<TaskDO> jobList = taskScheduleJobMapper.list(new HashMap<String, Object>(16));
        for (TaskDO scheduleJob : jobList) {
            if ("1".equals(scheduleJob.getJobStatus())) {
                ScheduleJob job = ScheduleJobUtils.entityToData(scheduleJob);
                quartzManager.addJob(job);
            }

        }
    }

    @Override
    public void changeStatus(Long jobId, String cmd) throws SchedulerException {
        TaskDO scheduleJob = get(jobId);
        if (scheduleJob == null) {
            return;
        }
        if (Constant.STATUS_RUNNING_STOP.equals(cmd)) {
            quartzManager.deleteJob(ScheduleJobUtils.entityToData(scheduleJob));
            scheduleJob.setJobStatus(ScheduleJob.STATUS_NOT_RUNNING);
        } else {
            if (!Constant.STATUS_RUNNING_START.equals(cmd)) {
            } else {
                scheduleJob.setJobStatus(ScheduleJob.STATUS_RUNNING);
                quartzManager.addJob(ScheduleJobUtils.entityToData(scheduleJob));
            }
        }
        update(scheduleJob);
    }

    @Override
    public void updateCron(Long jobId) throws SchedulerException {
        TaskDO scheduleJob = get(jobId);
        if (scheduleJob == null) {
            return;
        }
        if (ScheduleJob.STATUS_RUNNING.equals(scheduleJob.getJobStatus())) {
            quartzManager.updateJobCron(ScheduleJobUtils.entityToData(scheduleJob));
        }
        update(scheduleJob);
    }

}

啟動一個監(jiān)聽去初始化Quartz

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.bootdo.common.quartz.utils.QuartzManager;
import com.bootdo.common.service.JobService;

@Component
@Order(value = 1)
public class ScheduleJobInitListener implements CommandLineRunner {

    @Autowired
    JobService scheduleJobService;

    @Autowired
    QuartzManager quartzManager;

    @Override
    public void run(String... arg0) throws Exception {
        try {
            scheduleJobService.initSchedule();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市柜候,隨后出現(xiàn)的幾起案子搞动,更是在濱河造成了極大的恐慌,老刑警劉巖渣刷,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹦肿,死亡現(xiàn)場離奇詭異,居然都是意外死亡辅柴,警方通過查閱死者的電腦和手機箩溃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門瞭吃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涣旨,你說我怎么就攤上這事歪架。” “怎么了开泽?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵牡拇,是天一觀的道長。 經(jīng)常有香客問我穆律,道長惠呼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任峦耘,我火速辦了婚禮剔蹋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辅髓。我一直安慰自己泣崩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布洛口。 她就那樣靜靜地躺著矫付,像睡著了一般。 火紅的嫁衣襯著肌膚如雪第焰。 梳的紋絲不亂的頭發(fā)上买优,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音挺举,去河邊找鬼杀赢。 笑死,一個胖子當(dāng)著我的面吹牛湘纵,可吹牛的內(nèi)容都是我干的脂崔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼梧喷,長吁一口氣:“原來是場噩夢啊……” “哼砌左!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起铺敌,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绊困,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后适刀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秤朗,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年笔喉,在試婚紗的時候發(fā)現(xiàn)自己被綠了取视。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硝皂。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡悦即,死狀恐怖仍侥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸦做,我是刑警寧澤折欠,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布贝或,位于F島的核電站,受9級特大地震影響锐秦,放射性物質(zhì)發(fā)生泄漏咪奖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一酱床、第九天 我趴在偏房一處隱蔽的房頂上張望羊赵。 院中可真熱鬧,春花似錦扇谣、人聲如沸昧捷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽靡挥。三九已至,卻和暖如春鸯绿,著一層夾襖步出監(jiān)牢的瞬間跋破,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工楞慈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留幔烛,地道東北人啃擦。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓囊蓝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親令蛉。 傳聞我的和親對象是個殘疾皇子聚霜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359