Spring生態研習【一】:定時任務Spring-task


本系列具體研究一下spring生態中的重要或者常用的功能套件,今天從定時任務開始,主要是spring-task。至於quartz,下次找個時間再總結。

 

我的驗證環境,是SpringCloud體系下,基於SpringBoot進行的。Spring-boot的版本:1.5.4.release. JDK:1.8, 其他不多說。主要是基於注解的模式實現驗證,基於spring-boot嗎,就用他的約定大於配置以及注解配置。

 

今天重點介紹一下Spring task的三種典型的應用模式。實驗項目,基於IDEA進行,創建一個Spring-Task的父項目(project),然后分別創建相應的三種應用模式的子項目(Module),在父項目中進行基礎的pom.xml的配置。具體的創建過程,不是這里的重點,不做介紹。下面直接給出父項目的pom.xml內容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.roomdis</groupId>
    <artifactId>springtask</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>annotation-task</module>
        <module>change-scheduler</module>
        <module>dynamic-task</module>
    </modules>

    <name>SpringTask</name>
    <description>Spring Cloud project</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR7</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
View Code

 

1. 靜態定時

這種模式下,有三種典型的應用,即spring task的@Schedule注解的三個配置類型。cron,fixRate,fixDelay

1.1 pom.xml內容

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springtask</artifactId>
        <groupId>com.roomdis</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>annotation-task</artifactId>
    <packaging>jar</packaging>

    <name>annotation-task</name>
    <description>Spring Cloud project</description>
    <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-task-core</artifactId> </dependency> </dependencies>

</project>

基於Spring-cloud進行定時任務研究,最重要的核心依賴就是spring-cloud-task-core.

 

1.2 spring工程代碼

package com.roomdis.springtask.annotationtask;

import org.apache.log4j.Logger;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by chengsh05 on 2018-07-06.
 *
 * 重點研究Scheduled的幾個注解參數的用法:
 *
 * 可以看出該注解有三個方法或者叫參數,分別表示的意思是:
 *
 * cron:指定cron表達式
 *
 * fixedDelay:官方文檔解釋:An interval-based trigger where the interval is measured from the completion time of the previous task. The time unit value is measured in milliseconds.
* 即表示從上一個任務完成開始到下一個任務開始的間隔,單位是毫秒。 * * fixedRate:官方文檔解釋:An interval-based trigger where the interval is measured from the start time of the previous task. The time unit value is measured in milliseconds.
* 即從上一個任務開始到下一個任務開始的間隔,單位是毫秒。 *
*/ @Component public class SchTask { private Logger logger = Logger.getLogger(SchTask.class); @Scheduled(cron = "3/5 * * * * *") public void taskCron(){ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(); simpleDateFormat.applyPattern("yyyy-MM-dd HH:mm:ss"); String sdf = simpleDateFormat.format(new Date()); logger.info("cron: " + sdf); } @Scheduled(fixedRate = 3000) public void taskFixedRate(){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } SimpleDateFormat simpleDateFormat = new SimpleDateFormat(); simpleDateFormat.applyPattern("yyyy-MM-dd HH:mm:ss"); String sdf = simpleDateFormat.format(new Date()); logger.info("fixedRate: " + sdf); } @Scheduled(fixedDelay = 3000) public void taskFixedDelay(){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } SimpleDateFormat simpleDateFormat = new SimpleDateFormat(); simpleDateFormat.applyPattern("yyyy-MM-dd HH:mm:ss"); String sdf = simpleDateFormat.format(new Date()); logger.info("fixedDelay: " + sdf); } }

然后,再寫一個Springboot的啟動程序:

package com.roomdis.springtask;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * Created by chengsh05 on 2018-07-06.
 */
@SpringBootApplication
@EnableScheduling
public class AnnotationTaskApplication {

    public static void main(String []args) {
        SpringApplication.run(AnnotationTaskApplication.class, args);
    }
}

這個程序,啟動后,定時任務即開始運行,所以定義成靜態定時任務。且定時任務計划,即cron表達的信息固定不可變。

 

1.3 運行結果

注意,這里的定時任務SchTask里面,有3個不同的定時任務,我在測試中,分別將三個任務獨立啟動運行,得到了相應的輸出日志,供分析和驗證。從輸出的日志,可以輔助理解cron的定時規則,即cron的正則表達的書寫以及含義,后面將會附上介紹。獨立運行的日志如下:

cron:

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.4.RELEASE)

2018-07-06 19:25:28.089  INFO 8920 --- [           main] c.r.s.AnnotationTaskApplication          : Starting AnnotationTaskApplication on 60-361-0008 with PID 8920 (D:\Knowledge\SOURCE\spring-task\annotation-task\target\classes started by chengsh05 in D:\Knowledge\SOURCE\spring-task)
2018-07-06 19:25:28.091  INFO 8920 --- [           main] c.r.s.AnnotationTaskApplication          : No active profile set, falling back to default profiles: default
2018-07-06 19:25:28.147  INFO 8920 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2698dc7: startup date [Fri Jul 06 19:25:28 CST 2018]; root of context hierarchy
2018-07-06 19:25:28.974  INFO 8920 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-07-06 19:25:28.980  INFO 8920 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-07-06 19:25:28.989  INFO 8920 --- [           main] c.r.s.AnnotationTaskApplication          : Started AnnotationTaskApplication in 1.2 seconds (JVM running for 1.545)
2018-07-06 19:25:32.997 INFO 8920 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask : cron: 2018-07-06 19:25:32 2018-07-06 19:25:37.996 INFO 8920 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask : cron: 2018-07-06 19:25:37 2018-07-06 19:25:42.996 INFO 8920 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask : cron: 2018-07-06 19:25:42 2018-07-06 19:25:47.996 INFO 8920 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask : cron: 2018-07-06 19:25:47

fixRate:

2018-07-06 19:24:21.575  INFO 13360 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedRate: 2018-07-06 19:24:21
2018-07-06 19:24:24.572  INFO 13360 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedRate: 2018-07-06 19:24:24
2018-07-06 19:24:27.569  INFO 13360 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedRate: 2018-07-06 19:24:27
2018-07-06 19:24:30.566  INFO 13360 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedRate: 2018-07-06 19:24:30
2018-07-06 19:24:33.563  INFO 13360 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedRate: 2018-07-06 19:24:33
2018-07-06 19:24:36.560  INFO 13360 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedRate: 2018-07-06 19:24:36

fixDelay:

2018-07-06 19:26:37.492  INFO 14292 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedDelay: 2018-07-06 19:26:37
2018-07-06 19:26:42.488  INFO 14292 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedDelay: 2018-07-06 19:26:42
2018-07-06 19:26:47.484  INFO 14292 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedDelay: 2018-07-06 19:26:47
2018-07-06 19:26:52.480  INFO 14292 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedDelay: 2018-07-06 19:26:52
2018-07-06 19:26:57.476  INFO 14292 --- [pool-1-thread-1] c.r.springtask.annotationtask.SchTask    : fixedDelay: 2018-07-06 19:26:57

其中重點,對比fixRate和fixDelay,注意區分其含義,fixRate是以固定頻率啟動項目,即周期的計算是基於任務啟動的時間點,而fixDelay是基於任務執行結束的時間點進行周期。從輸出日志清晰的得到驗證。

 

1.4 cron模式正則信息說明

 Cron的正則表達,可以有下面的兩種模式,但是往往用到的第2種更多點。

1、Seconds Minutes Hours DayofMonth Month DayofWeek Year

2、Seconds Minutes Hours DayofMonth Month DayofWeek

例如上面,我們的例子中,3/5 * * * * *

從左到右,每一個字段都有一套可以指定有效值,如

Seconds (秒)         :可以用數字0-59 表示,

Minutes(分)          :可以用數字0-59 表示,

Hours(時)             :可以用數字0-23表示,

Day-of-Month(天) :可以用數字1-31 中的任一一個值,但要注意一些特別的月份

Month(月)            :可以用0-11 或用字符串  “JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC” 表示

Day-of-Week(每周):可以用數字1-7表示(1 = 星期日)或用字符口串“SUN, MON, TUE, WED, THU, FRI and SAT”表示

 

●星號(*):可用在所有字段中,表示對應時間域的每一個時刻,例如,*在分鍾字段時,表示“每分鍾”;

●問號(?):該字符只在日期和星期字段中使用,雖然我現在不知道它的值是多少,但是它的值是唯一的,通過日期可以推出星期,通過本周是周幾也可以推出日期。

●減號(-):表達一個范圍,如在小時字段中使用“10-12”,則表示從10到12點,即10,11,12;

●逗號(,):表達一個列表值,如在星期字段中使用“MON,WED,FRI”,則表示星期一,星期三和星期五;

●斜杠(/):x/y表達一個等步長序列,x為起始值,y為增量步長值。如在分鍾字段中使用0/15,則表示為0,15,30和45秒,而5/15在分鍾字段中表示5,20,35,50,你也可以使用*/y,它等同於0/y;

●L:該字符只在日期和星期字段中使用,代表“Last”的意思,但它在兩個字段中意思不同。L在日期字段中,表示這個月份的最后一天,如一月的31號,非閏年二月的28號;如果L用在星期中,則表示星期六,等同於7。但是,如果L出現在星期字段里,而且在前面有一個數值 X,則表示“這個月的最后X天”,例如,6L表示該月的最后星期五;

●W:該字符只能出現在日期字段里,是對前導日期的修飾,表示離該日期最近的工作日。例如15W表示離該月15號最近的工作日,如果該月15號是星期六,則匹配14號星期五;如果15日是星期日,則匹配16號星期一;如果15號是星期二,那結果就是15號星期二。但必須注意關聯的匹配日期不能夠跨月,如你指定1W,如果1號是星期六,結果匹配的是3號星期一,而非上個月最后的那天。W字符串只能指定單一日期,而不能指定日期范圍;

●LW組合:在日期字段可以組合使用LW,它的意思是當月的最后一個工作日;

●井號(#):該字符只能在星期字段中使用,表示當月某個工作日。如6#3表示當月的第三個星期五(6表示星期五,#3表示當前的第三個),而4#5表示當月的第五個星期三,假設當月沒有第五個星期三,忽略不觸發;

● C:該字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是計划所關聯的日期,如果日期沒有被關聯,則相當於日歷中所有日期。例如5C在日期字段中就相當於日歷5日以后的第一天。1C在星期字段中相當於星期日后的第一天。

 

2. 修改定時器(工程啟動后定時任務即運行,后續可以基於需要用REST API對定時規則進行修改)

2.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springtask</artifactId>
        <groupId>com.roomdis</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>changeshecduler</artifactId>
    <packaging>jar</packaging>

    <name>change-scheduler</name>
    <description>Spring Cloud project</description>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-task-core</artifactId>
        </dependency>
    </dependencies>

</project>

2.2 定時任務代碼

package com.roomdis.springtask.changescheduler.controller;

import org.apache.log4j.Logger;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

/**
 * Created by chengsh05 on 2018-07-06.
 */
@RestController
@EnableScheduling
public class SchController implements SchedulingConfigurer {

    private Logger logger = Logger.getLogger(SchController.class);

    /**
     * 定時任務的定時器表達式: 秒 分 時 日期 月 星期
     * 注意:有的地方說定時正則表達式可以有year,即7個元素,但是,在spring-boot里面,只能是6個元素,沒有年。
     */
    private String cronExpression = "1/5 * * * * *";

    /**
     * 通過REST API請求對參數進行修改,定時規則進行調整
     *
     * @param exp
     * @return
     */
    @RequestMapping("change")
    public String change(@RequestParam("exp") String exp) {
        cronExpression = exp;
        logger.info("new cron expression: " + exp);
        return cronExpression;
    }

    /**
     * 定時任務要執行的方法
     *
     * @return
     */
    private Runnable getTask() {
        Runnable task = new Runnable() {
            @Override
            public void run() {
                logger.info("Worker tell you the time: " + new Date());
            }
        };
        return task;
    }

    /**
     * 調度實現的時間控制
     *
     * @param scheduledTaskRegistrar
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        Trigger trigger=new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                CronTrigger cronTrigger=new CronTrigger(cronExpression);
                return cronTrigger.nextExecutionTime(triggerContext);
            }
        };
        scheduledTaskRegistrar.addTriggerTask(getTask(), trigger);
    }
}

基於springboot的主程序,在這里就不多寫了。注意,本工程中,cron的表達式,嘗試用7個字段的書寫方式,結果不行,即不能用有year的信息。錯誤如下:

java.lang.IllegalArgumentException: Cron expression must consist of 6 fields (found 7 in "1/5 * * * * * *")
    at org.springframework.scheduling.support.CronSequenceGenerator.parse(CronSequenceGenerator.java:265) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.support.CronSequenceGenerator.<init>(CronSequenceGenerator.java:96) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.support.CronSequenceGenerator.<init>(CronSequenceGenerator.java:83) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.support.CronTrigger.<init>(CronTrigger.java:44) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at com.roomdis.springtask.changescheduler.controller.SchController$2.nextExecutionTime(SchController.java:67) ~[classes/:na]
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.schedule(ReschedulingRunnable.java:68) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.concurrent.ConcurrentTaskScheduler.schedule(ConcurrentTaskScheduler.java:170) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.config.ScheduledTaskRegistrar.scheduleTriggerTask(ScheduledTaskRegistrar.java:385) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.config.ScheduledTaskRegistrar.scheduleTasks(ScheduledTaskRegistrar.java:344) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.config.ScheduledTaskRegistrar.afterPropertiesSet(ScheduledTaskRegistrar.java:330) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.finishRegistration(ScheduledAnnotationBeanPostProcessor.java:267) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:200) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:94) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:393) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:347) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:883) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.finishRefresh(EmbeddedWebApplicationContext.java:144) ~[spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at com.roomdis.springtask.changescheduler.ChangeSchApplication.main(ChangeSchApplication.java:15) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_77]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_77]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_77]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_77]
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) [idea_rt.jar:na]

2018-07-06 19:29:02.244  INFO 13776 --- [           main] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2bbaf4f0: startup date [Fri Jul 06 19:29:00 CST 2018]; root of context hierarchy
2018-07-06 19:29:02.247  INFO 13776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

 

2.3 輸出驗證

啟動主程序后,會看到前面的默認運行規則,即基於1/5 * * * * *得到的黑色加粗區域,然后,在瀏覽器的地址欄執行http://localhost:8080/change?exp=1/3 * * * * *,然后回車,即得到紅色區域的執行輸出:

2018-07-06 19:38:59.173  INFO 12704 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-07-06 19:38:59.183  INFO 12704 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-07-06 19:38:59.230  INFO 12704 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-07-06 19:38:59.233  INFO 12704 --- [           main] c.r.s.c.ChangeSchApplication             : Started ChangeSchApplication in 2.47 seconds (JVM running for 2.825)
2018-07-06 19:39:01.001  INFO 12704 --- [pool-1-thread-1] c.r.s.c.controller.SchController         : Worker tell you the time: Fri Jul 06 19:39:01 CST 2018
2018-07-06 19:39:06.000  INFO 12704 --- [pool-1-thread-1] c.r.s.c.controller.SchController         : Worker tell you the time: Fri Jul 06 19:39:06 CST 2018
2018-07-06 19:39:06.352  INFO 12704 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-07-06 19:39:06.352  INFO 12704 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-07-06 19:39:06.366  INFO 12704 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 14 ms
2018-07-06 19:39:06.389 INFO 12704 --- [nio-8080-exec-1] c.r.s.c.controller.SchController : new cron expression: 1/3 * * * * * 2018-07-06 19:39:11.001 INFO 12704 --- [pool-1-thread-1] c.r.s.c.controller.SchController : Worker tell you the time: Fri Jul 06 19:39:11 CST 2018 2018-07-06 19:39:13.000 INFO 12704 --- [pool-1-thread-1] c.r.s.c.controller.SchController : Worker tell you the time: Fri Jul 06 19:39:13 CST 2018 2018-07-06 19:39:16.001 INFO 12704 --- [pool-1-thread-1] c.r.s.c.controller.SchController : Worker tell you the time: Fri Jul 06 19:39:16 CST 2018

 

3. 動態啟動和停止以及修改定時規則

3.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springtask</artifactId>
        <groupId>com.roomdis</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>dynamic-task</artifactId>
    <description>Spring Cloud project</description>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-task-core</artifactId>
        </dependency>
    </dependencies>

</project>

 

3.2 主體工程代碼

package com.roomdis.springtask.dynamictask.controller;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.concurrent.ScheduledFuture;

/**
 * Created by chengsh05 on 2018-07-06.
 */
@RestController
@EnableScheduling
public class DynamicController {

    private Logger logger = Logger.getLogger(DynamicController.class);

    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    private ScheduledFuture<?> scheduledFuture;

    @Bean
    public ThreadPoolTaskScheduler getThreadPoolTaskScheduler(){
        return new ThreadPoolTaskScheduler();
    }

    private Runnable getTask() {
        Runnable task = new Runnable() {
            @Override
            public void run() {
                logger.info("Worker tell you the time: " + new Date());
            }
        };
        return task;
    }

    /**
     * 核心是利用ThreadPoolTaskScheduler的schedule()函數啟動,返回一個ScheduledFeature。
     *
     * @return
     */
    @RequestMapping("/start")
    public String startTask(){
        /**
         * task:定時任務要執行的方法
         * trigger:定時任務執行的時間
         */
        scheduledFuture = threadPoolTaskScheduler.schedule(getTask(), new CronTrigger("0/5 * * * * *") );
        logger.info("start task done");
        return "start task done";
    }

    /**
     * 核心是利用ScheduledFeature的cancel()函數。
     *
     * @return
     */
    @RequestMapping("stop")
    public String stopTask(){
        if(scheduledFuture != null){
            /**
             * ScheduledFeature繼承了jdk的接口Future, cancel用到參數true表示強制關閉任務。
             * cancel的參數false,表示允許任務執行完畢。
             * 因為這里是周期任務,沒有執行完畢的時候,所以用的是強制關閉任務。
             */
            scheduledFuture.cancel(true);
        }
        logger.info("stop task done");
        return "stop task done";
    }

    @RequestMapping("/change")
    public String changeTask(@RequestParam("exp") String exp){
        //1. 停止定時器
        stopTask();
        //2. 修改任務執行計划
        scheduledFuture=threadPoolTaskScheduler.schedule(getTask(), new CronTrigger(exp) );
        //3. 啟動定時器
        startTask();
        logger.info("change task done");
        return "change task done";
    }
}

代碼已經很清晰的表達了處理邏輯:

  • 其實,這個地方,主要是利用ThreadPoolTaskScheduler的功能,它可以schedule任務,參數是Runnable的task以及CronTrigger的定時觸發器最后利用schedule函數的返回值ScheduleFeature的cancel函數實現定時任務的停止定時任務的啟動,其實就是schedule()函數執行,就啟動了定時計划。通過外部REST API控制定時任務的創建和啟動,同樣也就可以實現通過REST API實現定時任務的停止。
  • 最重要的一個點是修改定時任務計划,這里,主要是先停止當前正在運行的任務,然后修改調度任務,最后,再啟動任務
  • 主程序啟動后,定時任務默認是不運行的,只有通過外部的控制,這里是REST API實現的定時任務的控制,這個特性非常重要,且有價值。

 

總結:

1. spring task應用非常有價值,且使用很簡單,只是要注意cron正則的書寫,且一定要注意使用技巧,上面有對cron的規則簡介。

2. spring task的定時任務,只能用在單機,確切的說是單應用的系統,在分布式系統里面,可以采用中心節點,對spring task的應用進行全局控制,當然,這個只是一種思路。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM