java-web系列(七)---SpringBoot整合Quartz實現多定時任務

前言

這個項目的github地址:extensible項目的github地址

extensible項目當前功能模塊如下:

java-web系列(一)—搭建一個基於SSM框架的java-web項目
java-web系列(二)—以dockerfile的方式發佈java-web項目
java-web系列(三)—(slf4j + logback)進行日誌分層
java-web系列(四)—幾種常見的加密算法
java-web系列(五)—SpringBoot整合Redis
java-web系列(六)—Mybatis動態多數據源配置
java-web系列(七)—SpringBoot整合Quartz實現多定時任務

如對該項目有疑問,可在我的博客/github下面留言,也可以以郵件的方式告知。
我的聯繫方式:[email protected]

定時任務的使用場景

在項目的開發過程中,我們經常會遇到類似這樣的需求:需要定期執行某一項工作,或者按照特定計劃執行某些工作。這時我們就需要用到定時任務的實現。

SpringBoot對定時任務的支持

日常開發中,定時任務最常用的實現方式有如下兩種:

  • Spring-3.*版本之後,自帶定時任務的實現
  • SpringBoot-2.*版本之後,均實現了Quartz的自動配置

Spring自帶定時任務的實現—@Scheduled註解

Spring自帶定時任務的實現,是靠@Scheduled註解實現的。在SpringBoot項目中,我們想使該註解生效,還需要在類上加註解@EnableScheduling,開啓對Spring定時任務的支持,源碼如下:

/**
 * @author zhenye 2018/9/21
 */
@Configuration
@EnableScheduling
@Slf4j
public class SpringScheduleConfig {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Scheduled(fixedRate = 1000 * 60L)
    public void runIntervalTask(){
        log.info("Spring3.*之後的自帶定時任務實現 ===> fixedRate表示按固定頻率執行該任務---打印當前時間:{}",sdf.format(new Date()));
    }

    @Scheduled(cron = "47 * * * * ?")
    public void runCornExpTask(){
        log.info("Spring3.*之後的自帶定時任務實現 ===> cron表示按corn表達式執行該任務---打印當前時間:{}",sdf.format(new Date()));
    }
}

上面的兩個定時任務就是基於註解@Scheduled實現的。

  • runIntervalTask(),是一個按固定頻率運行的任務,每1分鐘執行一次。
  • runCornExpTask(),是一個按corn表達運行的任務,每分鐘的第47秒運行一次。

啓動項目後,效果圖如下:

Spring自帶定時任務效果圖

SpringBoot整合Quartz

SpringBoot從2.0版本之後,就開啓了對Quartz自動配置的支持。
如:我使用的SpringBoot版本爲:2.0.4

SpringBoot-2.0支持Quartz

Quartz中最重要的幾個概念解釋:

  • Scheduler,調度器。作用:用來統一管理工作內容、工作計劃週期。
  • JobDetail,作業實例。作用:定義某項具體工作的工作內容。
  • Trigger,觸發器。作用:定義某項具體工作的執行頻率。

這些抽象概念不是那麼容易理解,我以“吃飯”來簡單講解一下它們的含義。

比如我給自己定了一個計劃表:每天8點到8點半吃早飯,每天12點到12點半吃午飯。用Quartz的概念解釋如下:

  • 這個計劃表,就是調度器(Scheduler),調度器告訴我們什麼時候該幹什麼。然後,早飯吃的是豆漿+油條,午飯吃的是米飯。
  • 早飯、午飯就是作業實例(JobDetail),作業實例是用來定義某項具體工作的工作內容。
  • 8點到8點半、12點到12點半就是兩個觸發器(Trigger),觸發器關聯某個作業實例,告訴該作業實例什麼時候進行作業。

從上面的事例說明,我們就很容易看出這三者之間的關係。

單任務的實現

SpringBoot-2.0開啓了Quartz的自動配置,因此單定時任務的實現非常簡單。直接上代碼:

首先,在pom文件中加入自動配置依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

然後,定義該定時任務的具體工作內容

@Slf4j
public class SimpleQuartzTask  extends QuartzJobBean{
    /**
     * 實現QuartzJobBean的executeInternal()方法,即可定義工作內容
     */
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("簡單的單Quartz任務開始---");
        try {
            Thread.sleep(4000L);
        } catch (InterruptedException e) {
            log.error("線程阻塞失敗。",e);
        }
        log.info("簡單的單Quartz任務結束---");
    }
}

最後,將該定時任務的工作內容和觸發器進行配置即可。

/**
 * @author zhenye 2018/9/26
 */
@Configuration
public class SimpleQuartzConfig {

    @Bean
    public JobDetail simpleQuartzTaskDetail(){
        return JobBuilder.newJob(SimpleQuartzTask.class).withIdentity("simpleQuartzTask").storeDurably().build();
    }

    @Bean
    public Trigger simpleQuartzTaskTrigger(){
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/3 * * * * ?");
        return TriggerBuilder.newTrigger().forJob(simpleQuartzTaskDetail())
                .withIdentity("simpleQuartzTask")
                .withSchedule(scheduleBuilder)
                .build();
    }
}

啓動項目,即可看到單定時任務的執行效果圖如下:

單定時任務的效果圖

多任務的實現

實際項目的開發過程中,可能要實現多個定時任務。如果用上面“單任務”的模板定義,定時任務的配置類就會顯得呆板複雜,程序可讀性也會很差

因此多任務的實現,需要換種方式進行配置。實際代碼如下:

首先,還是要在pom文件中導入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

然後,定義一個任務接口

/**
 * @author zhenye 2018/9/21
 */
public interface QuartzScheduleTask {
    /**
     * 該方法的實現,表示某個任務的具體工作內容
     */
    void exec();
}

在properties配置文件中配置quartz的部分參數

# quartz定時任務相關的配置
quartz.scheduler-name=QUARTZ_SCHEDULER
quartz.thread-count=10
quartz.thread-name-prefix=quartz_worker
quartz.tasks=\
    ReportTimeTask:*/10 * * * * ? *

進行自定義quartz的配置

/**
 * quartz定時任務的配置類
 * @author zhenye 2018/9/21
 */
@Configuration
@ConfigurationProperties("quartz")
@Getter
@Setter
public class QuartzScheduleConfig {

    private String schedulerName;
    private String threadCount;
    private String threadNamePrefix;
    private String tasks;
    private final ApplicationContext context;

    @Autowired
    public QuartzScheduleConfig(ApplicationContext context) {
        this.context = context;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        Properties properties = new Properties();
        properties.setProperty("org.quartz.threadPool.threadCount", threadCount);
        properties.setProperty("org.quartz.threadPool.threadNamePrefix", threadNamePrefix);
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setSchedulerName(schedulerName);
        factory.setQuartzProperties(properties);
        return factory;
    }

    @Bean
    public Scheduler scheduler() throws Exception {
        Scheduler scheduler = schedulerFactoryBean().getScheduler();
        scheduler.scheduleJobs(createJobDetails(), true);
        return scheduler;
    }

    /**
     * 創建JobDetail
     * 使用是Spring的MethodInvokingJobDetailFactoryBean來創建JobDetail
     * 使用Spring的ronTriggerFactoryBean來創建CronTrigger
     *
     * @return Map<JobDetail, Set<CronTrigger>>
     */
    private Map<JobDetail, Set<? extends Trigger>> createJobDetails(){
        Set<String> taskSet = StringUtils.commaDelimitedListToSet(tasks);
        Map<JobDetail, Set<? extends Trigger>> map = new HashMap<>(taskSet.size());
        for (String task : taskSet) {
            String[] nameAndCron = task.split(":");
            String name = StringUtils.uncapitalize(nameAndCron[0]);
            String cron = nameAndCron[1];
            MethodInvokingJobDetailFactoryBean factoryBean = new MethodInvokingJobDetailFactoryBean();
            factoryBean.setTargetObject(context.getBean(name));
            factoryBean.setName(name);
            factoryBean.setTargetMethod(QuartzScheduleTask.class.getMethods()[0].getName());
            factoryBean.afterPropertiesSet();
            CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
            cronTriggerFactoryBean.setCronExpression(cron);
            cronTriggerFactoryBean.setJobDetail(factoryBean.getObject());
            cronTriggerFactoryBean.setName(name);
            cronTriggerFactoryBean.afterPropertiesSet();
            Set<CronTrigger> cronTriggerSet = new HashSet<>(1);
            cronTriggerSet.add(cronTriggerFactoryBean.getObject());
            map.put(factoryBean.getObject(), cronTriggerSet);
        }
        return map;
    }
}

最後,進行某項具體任務中工作內容的實現

/**
 * @author zhenye 2018/9/21
 */
@Slf4j
@Component
public class ReportTimeTask implements QuartzScheduleTask{

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void exec() {
        log.info("執行的Quartz指定的任務,現在的時間是:{}",sdf.format(new Date()));
    }
}

這樣,就把多定時任務的配置簡化了,啓動項目可以看到如下效果圖:

多定時任務效果圖

然後,需要定義其他定時任務時,只需要兩步:

  1. 實現QuartzScheduleTask的exec()方法,定義該定時任務的工作內容;
  2. 在配置文件中加上該定時任務的觸發器。

多任務的配置說明:

SpringBoot是將一個定時任務看做一個方法。上面配置的做法是:

  • 首先,讀取配置文件中quartz.tasks中定義的任務名稱及該任務的觸發器。
  • 然後,通過ApplicationContext(應用上下文對象),根據任務名稱獲取到相應的任務對象實例。
  • 再然後,通過Spring的MethodInvokingJobDetailFactoryBean註冊JobDetail,該任務實例的第一個方法就是該任務的工作內容;通過Spring的CronTriggerFactoryBean註冊CornTrigger,該任務實例在配置文件中的內容,就是該任務的觸發器。
  • 把該任務的工作內容和觸發器放在Map中,通過schedule的scheduleJobs()方法將所有JobDetailTrigger添加到調度器中。
  • 這樣SpringBoot就會幫我們管理調度器schedule實例,並自動按計劃執行相應的定時任務。

這裏還需要註明一點:配置了多任務後,之前的單任務會失效。因爲自定義的調度器Schdule中,並沒有加入該單任務對應的JobDetaiTrigger,所以該調度器不會管理該單任務。

corn表達式簡介

不管是Spring自帶的定時任務實現,還是SpringBoot整合Quartz的定時任務實現,其觸發器都支持用corn表達式來表示。

corn表達式是一個字符串,有6或7個域,域之間是用空格進行間隔。

從左到右每個域表示的含義如下:

第幾個域 英文釋義 允許值 備註
Seconds 0~59
Minutes 0~59
Hours 0~23
DayOfMonth 1-31
Month 1-12或月份簡寫
DayOfWeek 1-7或星期簡寫 星期,1表示SUN
Year 1970~2099

然後,某些域還支持部分特殊字符,特殊字符的含義如下:

特殊字符 含義及注意事項
* 任意值
? 佔位符,只能在第四域和第六域中使用
- 區間,表示區間內有效
/ 固定間隔
, 枚舉有效值的間隔符
L 表示該區間的最後一個有效值,只能在第四域和第六域中使用
W 表示離指定日期的最近的有效工作日,(週一-週五爲工作日)

常用corn表達式例子含義說明:

corn表示式 表達式含義
*/10 * * * * ? * 每隔10秒執行一次
0 30 1 * * ? * 每天凌晨1點30分0秒開始執行
0 0 10,14,16 * * ? 每天10點、14點、16點執行一次
0 15 10 L * ? 每個月最後一天的10點15分執行一次
0 15 10 ? * 6L 2018-2020 2018年到2020年每個月最後一個星期五的10:15執行
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章