定时任务应该这么玩

使用 XAML 格式化工具:XAML Styler

1.场景

在电商系统中会经常遇到这样一种场景,就是商品的定时上下架功能,总不能每次都手动执行吧,这个时候我们首先想到的就是利用定时任务来实现这个功能。

目前实现定时任务主要有以下几种方式:

  • JDK自带 :JDK自带的Timer以及JDK1.5+ 新增的ScheduledExecutorService;

  • 第三方框架 :使用 Quartz、elastic-job、xxl-job 等开源第三方定时任务框架,适合分布式项目应用。该方式的缺点是配置复杂。

  • Spring :使用 Spring 提供的一个注解 @Schedule,开发简单,使用比较方便。

本文博主主要向大家介绍Quartz框架和Spring定时任务的使用。

2.什么是Quartz

Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。

Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。

Quartz 允许程序开发人员根据时间的间隔来调度作业。

Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。

3.Quartz几个核心概念

在正式学习使用Quartz之前,我们需要了解几个有关Quartz的核心概念,方便我们后面学习

  1. Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:

    void execute(JobExecutionContext context) 
    // context是重要的上下文,可以访问到关联的JobDetail对象和本次触发的Trigger对象,以及在此之上设定的数据。
    
  2. JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。

  3. Trigger 代表一个调度参数的配置,什么时候去调。

  4. Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

4.Quartz初体验

一、创建一个SpringBoot项目,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.songguoliang</groupId>
    <artifactId>spring-boot-quartz</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>spring-boot-quartz</name>
    <description>Spring Boot使用Quartz定时任务</description>

    <!-- Spring Boot启动器父类 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

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

    <dependencies>
        <!-- Spring Boot web启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- quartz -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

    </dependencies>

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

二、创建一个Job(Job里面是要执行的具体内容)

package com.example.quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;

import java.time.LocalDateTime;

public class TestJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 通过context获取trigger中的数据
        Object tv1 = context.getTrigger().getJobDataMap().get("t1");
        Object tv2 = context.getTrigger().getJobDataMap().get("t2");
        // 通过context获取JobDetail中的数据
        Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
        Object jv2 = context.getJobDetail().getJobDataMap().get("j2");
        Object sv = null;
        try {
            sv = context.getScheduler().getContext().get("skey");
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        System.out.println(tv1+":"+tv2);
        System.out.println(jv1+":"+jv2);
        System.out.println(sv);
        System.out.println("date:"+ LocalDateTime.now());
    }
}

三、执行Job

package com.example.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzTest {
    public static void main(String[] args)  {
        try {
            //创建一个scheduler
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            //向scheduler中put值
            scheduler.getContext().put("skey", "svalue");

            //创建一个Trigger
            Trigger trigger = TriggerBuilder.newTrigger()
                    //给该Trigger起一个id
                    .withIdentity("trigger1")
                    //以Key-Value形式关联数据
                    .usingJobData("t1", "tv1")
                    //每3秒触发一次,无限循环
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                            .repeatForever()).build();
            trigger.getJobDataMap().put("t2","tv2");

            //创建一个JobDetail
            JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
                    //给该JobDetail起一个id
                    .withIdentity("myJob", "myGroup")
                    .usingJobData("j1", "jv1")
                    .build();
            jobDetail.getJobDataMap().put("j2", "jv2");

            //注册trigger并启动scheduler
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
            //如果想要停止这个Job,可以调用shutdown方法
            //scheduler.shutdown();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

控制台输出

10:46:54.075 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
10:46:54.079 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
10:46:54.089 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
10:46:54.089 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
10:46:54.090 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
10:46:54.091 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

10:46:54.091 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
10:46:54.091 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
10:46:54.104 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
10:46:54.104 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:46:54.106 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob
10:46:54.110 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:46:54.110 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
tv1:tv2
jv1:jv2
svalue
date:2020-12-19T10:46:54.144
10:46:57.092 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob
10:46:57.092 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:46:57.092 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
tv1:tv2
jv1:jv2
svalue
date:2020-12-19T10:46:57.092
10:47:00.101 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob
10:47:00.101 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:47:00.101 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
tv1:tv2
jv1:jv2
svalue
date:2020-12-19T10:47:00.101
10:47:03.096 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob
10:47:03.096 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:47:03.096 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
tv1:tv2
jv1:jv2
svalue
date:2020-12-19T10:47:03.096

从输出结果我们可以看到此Job每隔3秒执行一次

有关概念

1、Job

job的一个 trigger 被触发后(稍后会讲到),execute() 方法会被 scheduler 的一个工作线程调用;传递给 execute() 方法的 JobExecutionContext 对象中保存着该 job 运行时的一些信息 ,执行 job 的 scheduler 的引用,触发 job 的 trigger 的引用,JobDetail 对象引用,以及一些其它信息。

2、JobDetail

JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap

3、Trigger

Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你创建一个 Trigger 的实例,然后设置调度相关的属性。Trigger 也有一个相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。SimpleTrigger 主要用于一次性执行的 Job(只在某个特定的时间点执行一次),或者 Job 在特定的时间点执行,重复执行 N 次,每次执行间隔T个时间单位。CronTrigger 在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午 10:15”等。

5.JobDetail详解

在定义一个Job时,我们需要实现Job接口,该接口只有一个execute方法。

从上一节的案例中我们可以发现,我们通过Scheduler去执行Job,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job。(这里利用了Java中的反射创建实例对象)每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。

那么我们该如何给Job配置相关属性呢?答案就是通过JobDetail

JobDataMap

JobDataMap实现了Map接口,可以存放键值对数据,在Job执行的时候,我们就可以通过JobExecutionContext获取到JobDataMap中的数据,如下

理解C#中的 async await

JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
                    .withIdentity("myJob", "myGroup")
                    .usingJobData("j1", "jv1")
                    .usingJobData("j2","jv2")
                    .build();

在job的执行过程中,可以从JobDataMap中取出数据,如下示例:

Object jv1 = context.getJobDetail().getJobDataMap().get("j1");

当然,如果你希望实现属性的自动注入,那么你可以使用下面的方法

package com.example.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzTest2 {
    public static void main(String[] args)  {
        try {

            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1")
                    .usingJobData("t1", "tv1")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                            .repeatForever())
                    .build();

            JobDetail jobDetail = JobBuilder.newJob(TestJob2.class)
                    .withIdentity("jd")
                    .usingJobData("name", "张三")
                    .usingJobData("age", 12)
                    .build();

            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}
package com.example.quartz;

import org.quartz.*;

public class TestJob2 implements Job {

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey jobKey = context.getJobDetail().getKey();

        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();

        System.out.println("name:" + name + "age:" +age);
    }
}

给Job类加上get和set方法(属性名称要和JobDataMap中的key相同),那么JobDataMap中的值就是自动注入到Job中,不需要手动获取

6.Triggers详解

Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你创建一个 Trigger 的实例,然后设置调度相关的属性。所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。

triggers公共属性

  • jobKey属性:当trigger触发时被执行的job的身份;
  • startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
  • endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。

优先级(priority)

如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。

注意:只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。

注意:如果trigger是可恢复的,在恢复后再调度时,优先级与原trigger是一样的。

错过触发(misfire Instructions)

trigger还有一个重要的属性misfire;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为

Simple Trigger

SimpleTrigger简单点说,就是在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。类似于闹钟,你定了一个周末早晨7点的闹钟,这个闹钟会在周末早上7点准时响起。闹钟还有个功能就是过5分钟之后再响一次,这对应着指定的间隔重复执行若干次。

1、指定时间开始触发,不重复:

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                    .withIdentity("st1", "group1")
                    .startAt(new Date()) // 从当前时间开始执行一次,不重复
                    .build();

2、指定时间触发,每隔2秒执行一次,重复5次:

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st2", "group1")
        .startAt(new Date())
        .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(2) // 2秒
                .withRepeatCount(5)// 5次
        )
        .build();

3、1分钟以后开始触发,仅执行一次:

long time = 1 * 60 * 1000;
Date now = new Date();
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st3", "group1")
        .startAt(new Date(now.getTime() + time))
        .build();

4、立即触发,每隔2秒钟执行一次,直到2020-12-19 13:20:00

String dateStr="2020-12-19 13:20:00";
String pattern="yyyy-MM-dd HH:mm:ss";
SimpleDateFormat dateFormat=new SimpleDateFormat(pattern);
Date date = dateFormat.parse(dateStr);
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st4", "group1")
        .withSchedule(SimpleScheduleBuilder.simpleSchedule()
        .withIntervalInSeconds(2)
        .repeatForever())
        .endAt(date)
        .build();

5、在13:00触发,然后每2小时重复一次:

String dateStr="2020-12-19 13:00:00";
String pattern="yyyy-MM-dd HH:mm:ss";
SimpleDateFormat dateFormat=new SimpleDateFormat(pattern);
Date date = dateFormat.parse(dateStr);
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st2", "group1")
        .withSchedule(SimpleScheduleBuilder.simpleSchedule()
        .withIntervalInHours(2)
        .repeatForever())
        .build();

SimpleTrigger Misfire

misfire:被错过的执行任务策略

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st6")
        .withSchedule(
                SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInMinutes(5)
                        .repeatForever()
                        .withMisfireHandlingInstructionNextWithExistingCount()
        )
        .build();

CronTrigger

CronTrigger通常比Simple Trigger更有用,如果你需要在指定日期执行某项任务,使用CronTrigger就非常方便,比如如果你想在每月的15号给会员发放优惠券,或者每周五中午12点统计用户本周使用产品时长。

cron 表达式是一个字符串,该字符串由 6 个空格分为 7 个域,每一个域代表一个时间含义。 通常定义 “年” 的部分可以省略,实际常用的 Cron 表达式由前 6 部分组成。格式如下

 [秒] [分] [时] [日] [月] [周] [年]
 Seconds  Minutes  Hours   Day-of-Month  Month   Day-of-Week	Year (optional field)
是否必填 值以及范围 通配符
0-59 , – * /
0-59 , – * /
0-23 , – * /
1-31 , – * ? / L W
1-12 或 JAN-DEC , – * /
1-7 或 SUN-SAT , – * ? / L #
1970-2099 , – * /

需要说明的是,Cron 表达式中,“周” 是从周日开始计算的。“周” 域上的 1 表示的是周日,7 表示周六。

每天晚上12点触发任务:0 0 0 * * ?

每隔 1 分钟执行一次:0 */1 * * * ?

每月 1 号凌晨 1 点执行一次:0 0 1 1 * ?

每月最后一天 23 点执行一次:0 0 23 L * ?

每周周六凌晨 3 点实行一次:0 0 3 ? * L

在24分,30分执行一次:0 24,30 * * * ?

是不是有点没看懂,没关系,我们可以使用Cron表达式生成器帮助我们生成Cron表达式

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?"))
        .build();

7.@Schedule实现定时任务

很多时候我们都需要为系统建立一个定时任务来帮我们做一些事情,SpringBoot 已经帮我们实现好了一个,我们只需要直接使用即可

一、引入依赖

<dependencies>

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

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

 <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
 </dependency>

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

</dependencies>

二、开启注解

在 SpringBoot 中我们只需要在启动类上加上@EnableScheduling便可以启动定时任务了。

@SpringBootApplication
@EnableScheduling
public class TaskApplication {

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

三、创建scheduled task

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

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

/**
 * @author wugongzi
 */
@Component
public class ScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    /**
     * fixedRate:固定速率执行。每5秒执行一次。
     */
    @Scheduled(fixedRate = 5000)
    public void reportCurrentTimeWithFixedRate() {
        log.info("Current Thread : {}", Thread.currentThread().getName());
        log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date()));
    }

    /**
     * fixedDelay:固定延迟执行。距离上一次调用成功后2秒才执。
     */
    @Scheduled(fixedDelay = 2000)
    public void reportCurrentTimeWithFixedDelay() {
        try {
            TimeUnit.SECONDS.sleep(3);
            log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * initialDelay:初始延迟。任务的第一次执行将延迟5秒,然后将以5秒的固定间隔执行。
     */
    @Scheduled(initialDelay = 5000, fixedRate = 5000)
    public void reportCurrentTimeWithInitialDelay() {
        log.info("Fixed Rate Task with Initial Delay : The time is now {}", dateFormat.format(new Date()));
    }

    /**
     * cron:使用Cron表达式。 每分钟的1,2秒运行
     */
    @Scheduled(cron = "1-2 * * * * ? ")
    public void reportCurrentTimeWithCronExpression() {
        log.info("Cron Expression: The time is now {}", dateFormat.format(new Date()));
    }
}

启动项目便可以看到效果。

开篇:免费开源的趣讲 ZooKeeper 教程(连载)

给TA买糖
共{{data.count}}人
人已赞赏
经验教程

【小菜学网络】交换机与MAC地址学习

2021-1-21 8:36:00

经验教程

使用 XAML 格式化工具:XAML Styler

2021-1-21 9:06:00

⚠️
免责声明:根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。 本站为个人博客非盈利性站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途,网站会员捐赠是您喜欢本站而产生的赞助支持行为,仅为维持服务器的开支与维护,全凭自愿无任何强求。本站部份代码及教程来源于互联网,仅供网友学习交流,若您喜欢本文可附上原文链接随意转载。
无意侵害您的权益,请发送邮件至 momeis6@qq.com 或点击右侧 私信:momeis 反馈,我们将尽快处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索