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的应用进行全局控制,当然,这个只是一种思路。

猜你喜欢

转载自www.cnblogs.com/shihuc/p/9275780.html