本系列具体研究一下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:KnowledgeSOURCEspring-taskannotation-task argetclasses started by chengsh05 in D:KnowledgeSOURCEspring-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的应用进行全局控制,当然,这个只是一种思路。