本系列具體研究一下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>
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的應用進行全局控制,當然,這個只是一種思路。
