【SpringBoot】 定时任务之任务执行和调度及使用指南

news2024/9/23 1:37:36

【SpringBoot】 定时任务之任务执行和调度及使用指南

Spring框架分别通过TaskExecutor和TaskScheduler接口为任务的异步执行和调度提供了抽象。Spring还提供了支持应用程序服务器环境中的线程池或CommonJ委托的那些接口的实现。最终,在公共接口后面使用这些实现,消除了JavaSE5、JavaSE6和JakartaEE环境之间的差异。

Spring还具有集成类,以支持Timer(自1.3以来JDK的一部分)和Quartz Scheduler的调度。您可以分别使用FactoryBean和可选的Timer或Trigger实例引用来设置这两个调度器。此外,Quartz Scheduler和Timer都有一个方便类,它允许您调用现有目标对象的方法(类似于普通的MethodInvokingFactoryBean操作)。

本文将着重介绍TaskScheduler接口、TaskExecutor接口以及Spring中定时任务的正确使用。

一、ThreadPoolExecutor和ThreadPoolTaskExecutor

ThreadPoolExecutorThreadPoolTaskExecutor很多人容易把这两个搞混。

我们实际开发中更多的是使用SpringBoot来开发,Spring默认自带一个线程池方便我们开发,它就是ThreadPoolTaskExecutor,ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理。

1.1 ThreadPoolExecutor

ThreadPoolExecutor这个类是JDK中的线程池类,继承自Executor。 Executor 顾名思义是专门用来处理多线程相关的一个接口,所有线程相关的类都实现了这个接口,里面有一个execute()方法,用来执行线程。ExecutorService为线程池接口,提供了线程池生命周期方法,继承自Executor接口,ThreadPoolExecutor为线程池实现类,提供了线程池的维护操作等相关方法,继承自AbstractExecutorService,AbstractExecutorService实现了ExecutorService接口。

线程池主要提供一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁的额外开销,提高了响应的速度

ThreadPoolExecutor
在这里插入图片描述

1.2 ThreadPoolTaskExecutor

ThreadPoolTaskExecutor则是spring包下的,是sring为我们提供的线程池类,对ThreadPoolExecutor进行封装,消除了JavaSE5、JavaSE6和JakartaEE环境之间的差异。

二、 Spring TaskExecutor

执行器是线程池概念的JDK名称。executor命名是因为无法保证底层实现实际上是一个池。执行器可以是单线程的,甚至可以是同步的。Spring的抽象隐藏了JavaSE和JakartaEE环境之间的实现细节。

**Spring的TaskExecutor接口与java.util.concurrent.Executor接口相同。事实上,最初,它存在的主要原因是在使用线程池时不需要Java5。**该接口有一个方法execute(Runnable task),该方法根据线程池的语义和配置接受要执行的任务。

创建TaskExecutor最初是为了在需要时为其他Spring组件提供线程池抽象。ApplicationEventMulticaster、JMS的AbstractMessageListenerContainer和Quartz集成等组件都使用TaskExecutor抽象来池线程。然而,如果您的bean需要线程池行为,您也可以根据自己的需要使用此抽象。

2.1 TaskExecutor 默认实现

Spring包括许多预先构建的TaskExecutor实现。很可能,你永远不需要实现你自己的。Spring提供的变体如下:

  • SyncTaskExecutor:此实现不会异步运行调用。相反,每次调用都发生在调用线程中。它主要用于不需要多线程的情况,例如在简单的测试用例中。
  • SimpleAsyncTaskExecutor:此实现不重用任何线程。相反,它为每个调用启动一个新线程。然而,它确实支持一个并发限制,即在释放槽之前阻止任何超过该限制的调用。如果您正在寻找真正的池,请参阅此列表后面的ThreadPoolTaskExecutor。
  • ConcurrentSkExecutor:此实现是java.util.concurrent.Executor实例的适配器。还有一种替代方法(ThreadPoolTaskExecutor)将Executtor配置参数公开为bean财产。很少需要直接使用ConcurrentTaskExecutor。但是,如果ThreadPoolTaskExecutor不够灵活,无法满足您的需要,则ConcurrentTaskExecutor是另一种选择。
  • ThreadPoolTaskExecutor此实现最常用。它公开用于配置java.util.concurrent.ThreadPoolExecutor的bean生产,并将其包装在TaskExecutor中。如果您需要适应不同类型的java.util.concurrent.Executor,我们建议您改用ConcurrentSkExecutor。
  • DefaultManagedTaskExecutor:此实现在JSR-236兼容的运行时环境(如Jakarta EE应用程序服务器)中使用JNDI获得的ManagedExecutorService,以取代CommonJ WorkManager。

2.2 TaskExecutor 使用

在下面的示例中,我们定义了一个bean,它使用ThreadPoolTaskExecutor异步打印一组消息。

首先,在配置类中注入ThreadPoolTaskExecutor的bean实例。

@Configuration
public class ThreadPoolConfig {
  
    @Bean
    private ThreadPoolTaskExecutor execThreadPoolTaskExecutor() {
            ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
            pool.setCorePoolSize(10);  // 核心线程数
            pool.setMaxPoolSize(50);  // 最大线程数
            pool.setQueueCapacity(500);  // 等待队列size
            pool.setKeepAliveSeconds(60);  // 线程最大空闲存活时间
            pool.setWaitForTasksToCompleteOnShutdown(true);
            pool.setAwaitTerminationSeconds(60);  // 程序shutdown时最多等60秒钟让现存任务结束
            pool.setRejectedExecutionHandler(new CallerRunsPolicy());  // 拒绝策略
            return pool;
    }
}

然后,在业务逻辑类中引用ThreadPoolTaskExecutor的bean示例处理业务逻辑。

import org.springframework.core.task.TaskExecutor;

@Service
public class TaskExecutorService {

  @Resource(name = "execThreadPoolTaskExecutor")
  private ThreadPoolTaskExecutor execThreadPoolTaskExecutor;

  public void execLogic() {
    for(int i = 0; i < 25; i++) {
      execThreadPoolTaskExecutor.execute(() -> System.ount.println("exec logic" + i));
    }
  }
}

2.3 Jboss EnhancedQueueExecutor

在JDK线程池中自带的Executor遵循一种典型的生产者,消费者队列模型,即一个统一的阻塞队列,然后一个线程数组不停地消费其中的数据。其本身的处理逻辑为 coreSize->queueSize->maxSize 的增长方式,即先尝试增加 coreSize, 然后再不断地将任务放进队列中,如果队列满了,则再尝试增加 maxSize, 直至拒绝任务。

通过一些手法可以调整策略为 coreSize->maxSize->queueSize。

此次使用 jboss-threads 中的 EnhancedQueueExecutor,中文为增加型队列执行器。其除支持典型的executor模型外,也同样保留如 coreSize,maxSize, queueSize 这些模型。与jdk中实现相区别的是,其本身采用单个链表来完成任务的提交和线程的执行,同时采用额外的数据来存储计数类数据. 更重要的是,其默认线程策略即 coreSize->maxSize->queueSize, 同时可以根据参数调整此策略。

创建对象与ThreadPoolExecutor类似,指定相应的参数即可,如下所示:

@Configuration
@Slf4j
public class EnhancedQueueExecutorConfig {
  
  Thread.UncaughtExceptionHandler uncaughtExceptionHandler = (t, e) -> log.error("任务失败: {}",
                e.getMessage(), e);

  var threadFactory = new ThreadFactoryBuilder().setNameFormat("myExecutor" + "-%d")
                .setUncaughtExceptionHandler(uncaughtExceptionHandler)
                .build();
  
  EnhancedQueueExecutor executor = new EnhancedQueueExecutor.Builder()
          .setCorePoolSize(corePoolSize)
          .setMaximumPoolSize(maxPoolSize)
          .setKeepAliveTime(Duration.ofMinutes(5))
          .setMaximumQueueSize(1024)
          .setThreadFactory(threadFactory)
          .setExceptionHandler(uncaughtExceptionHandler)
          .setRegisterMBean(false)
          .setGrowthResistance(growthResistance) //增长因子,控制新线程创建逻辑(if >= coreSize时)
          .build();
}

三、Spring的TaskScheduler接口详解

除了TaskExecutor抽象之外,Spring还有一个TaskScheduler API,它具有多种方法来调度将来某个时刻运行的任务。

TaskScheduler接口定义:

public interface TaskScheduler { 
 
    /** 
     * 提交任务调度请求 
     * @param task 待执行任务   
     * @param trigger 使用Trigger指定任务调度规则 
     * @return
     */
    @Nullable
    ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
    /** 
     * 提交任务调度请求 
     * 注意任务只执行一次,使用startTime指定其启动时间 
     * @param task 待执行任务 
     * @param startTime 任务启动时间 
     * @return 
     */
    ScheduledFuture<?> schedule(Runnable task, Instant startTime);
    /** 
     * 使用fixedRate的方式提交任务调度请求 
     * 任务首次启动时间由传入参数指定 
     * @param task 待执行的任务  
     * @param startTime 任务启动时间 
     * @param period 两次任务启动时间之间的间隔时间,默认单位是毫秒 
     * @return 
     */
    ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
    /** 
     * 使用fixedRate的方式提交任务调度请求 
     * 任务首次启动时间未设置,任务池将会尽可能早的启动任务 
     * @param task 待执行任务 
     * @param period 两次任务启动时间之间的间隔时间,默认单位是毫秒 
     * @return 
     */
    ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period);
    /** 
     * 使用fixedDelay的方式提交任务调度请求 
     * 任务首次启动时间由传入参数指定 
     * @param task 待执行任务 
     * @param startTime 任务启动时间 
     * @param delay 上一次任务结束时间与下一次任务开始时间的间隔时间,单位默认是毫秒 
     * @return 
     */
    ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
    /** 
     * 使用fixedDelay的方式提交任务调度请求 
     * 任务首次启动时间未设置,任务池将会尽可能早的启动任务 
     * @param task 待执行任务 
     * @param delay 上一次任务结束时间与下一次任务开始时间的间隔时间,单位默认是毫秒 
     * @return 
     */
    ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay);
}

最简单的方法是一个名为schedule的方法,它只需要一个Runnable和一个Instant。这会导致任务在指定时间后运行一次。所有其他方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的周期性执行,但接受触发器的方法要灵活得多。

3.1 Trigger 接口

Trigger接口本质上受到JSR-236的启发。触发器的基本思想是,可以根据过去的执行结果甚至任意条件来确定执行时间。如果这些确定考虑了先前执行的结果,则该信息在TriggerContext中可用。Trigger接口用于计算任务的下次执行时间。

Trigger接口本身非常简单,如下表所示:

public interface Trigger {

    Instant nextExecution(TriggerContext triggerContext);
}

TriggerContext是最重要的部分。它封装了所有相关数据,如果需要,将来可以进行扩展。TriggerContext是一个接口(默认使用SimpleTriggerContext实现)。下面的列表显示了Trigger实现的可用方法。

public interface TriggerContext {

    Clock getClock();

    Instant lastScheduledExecution();

    Instant lastActualExecution();

    Instant lastCompletion();
}

3.2 Trigger 接口实现

Spring提供了Trigger接口的两种实现CronTriggerPeriodicTrigger

3.2.1 CronTrigger

最有趣的是CronTrigger。它支持基于cron表达式的任务调度。通过Cron表达式来生成调度计划。

例如,以下任务计划在每小时15分钟后运行,但仅在工作日的朝九晚五“工作时间”内运行:

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

3.2.2 PeriodicTrigger

用于定期执行的Trigger;它有两种模式:

  • fixedRate:两次任务开始时间之间间隔指定时长
  • fixedDelay: 上一次任务的结束时间与下一次任务开始时间间隔指定时长

可见这两种情况的区别就在于,在决定下一次的执行计划时是否要考虑上次任务在什么时间执行完成。 默认情况下PeriodicTrigger使用了fixedDelay模式。

PeriodicTrigger提供以下参数来达成目的:

  • period: Duration类型,表示间隔时长,注意在fixedRate与fixedDelay两种模式下的不同含义
  • chronoUnit: ChronoUnit类型,计时单元
  • initialDelay: Duration类型,表示启动任务后间隔多长时间开始执行第一次任务
  • fixedRate: boolean类型,表示是否是fixedRate,为true时是fixedRate,否则是fixedDelay,默认为false

PeriodicTrigger接受一个固定的周期、一个可选的初始延迟值和一个布尔值,以指示该周期应该被解释为固定速率还是固定延迟。由于TaskScheduler接口已经定义了以固定速率或固定延迟调度任务的方法,因此应尽可能直接使用这些方法。PeriodicTrigger实现的价值在于,您可以在依赖Trigger抽象的组件中使用它。例如,允许交替使用周期性触发器、基于cron的触发器,甚至自定义触发器实现可能很方便。这样的组件可以利用依赖注入,这样您就可以在外部配置这样的触发器,从而轻松地修改或扩展它们。

3.3 TaskScheduler 的实现类

3.3.1 TimerManagerTaskScheduler

用于包装CommonJ中的TimerManager接口。在使用CommonJ进行调度时使用。

3.3.2 ThreadPoolTaskScheduler

包装Java Concurrent中的ScheduledThreadPoolExecutor类,大多数场景下都使用它来进行任务调度。 除实现了TaskScheduler接口中的方法外,它还包含了一些对ScheduledThreadPoolExecutor进行操作的接口,其常用方法如下:

  • setPoolSize 设置线程池大小,最小为1,默认情况下也为1;
  • setErrorHandler 设置异常处理器。
  • getScheduledThreadPoolExecutor 获取ScheduledExecutor,默认是ScheduledThreadPoolExecutor类型。
  • getActiveCount 获取当前活动的线程数
  • execute 提交执行一次的任务
  • submit\submitListenable 提交执行一次的任务,并且返回一个Future对象供判断任务状态使用。

与Spring的TaskExecutor抽象一样,TaskScheduler抽象的主要好处是应用程序的调度需求与部署环境分离。当部署到应用程序服务器环境时,这个抽象级别尤其重要,因为应用程序本身不应该直接创建线程。对于这样的场景,Spring提供了一个TimerManagerTaskScheduler,它委托给WebLogic或WebSphere上的CommonJ TimerManager,以及一个更新的DefaultManagedTaskScheduler,在Jakarta EE环境中委托给JSR-236 ManagedScheduledExecutorService。两者通常都配置有JNDI查找。

每当不需要外部线程管理时,一个更简单的替代方案就是在应用程序中设置本地ScheduledExecutorService,它可以通过Spring的ConcurrentTaskScheduler进行调整。为了方便起见,Spring还提供了ThreadPoolTaskScheduler,它在内部委托给ScheduledExecutorService,以提供与ThreadPoolTaskExecutor类似的通用bean样式配置。这些变体对于宽松的应用程序服务器环境中的本地嵌入式线程池设置也非常适用 — 特别是在Tomcat和Jetty上。

四、调度和异步执行的注解支持

Spring 为任务调度和异步方法提供了注释支持 执行。

4.1 启用调度注解

必须要使用@EnableScheduling注解来启用对@Scheduled注解的支持,@EnableScheduling必须使用在工程中某一个被Configuration注解的类上,当然程序的主启启动类上也可以,因为程序主启动类底层也是含有Configuration注解。

如下例所示:

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

您可以选择应用程序的相关注释。例如,如果只需要对@Scheduled的支持,则可以省略@EnableAsync。对于更细粒度的控制,可以另外实现SchedulingConfigurer接口、AsyncConfigurer接口或两者。有关详细信息,请参阅SchedulingConfigurer和AsyncConfigurer javadoc。

4.2 @Scheduled注解

Scheduled注解用在方法上,用于表示这个方法将会被调度。不同于Async注解,它所注解的方法返回类型最好是void类型的,否则它的返回值将不会被TaskScheduler所使用。同时,被它注解的方法不能有参数。如果要使用其它的对象的值,需要通过依赖注入的方式引用。

它包含有以下属性:

  • cron: 使用Cron语法来指定调度计划
  • zone: 指定时区,默认为本地时区
  • fixedDelay: 指定fixedDelay的值,它表示上一次任务执行完后多长时间启动下一次任务,单位默认是毫秒
  • fixedRate: 指定上一次任务开始时间到下一次任务开始时间的间隔时间,单位默认是毫秒
  • initialDelay: 指定提交调度任务后多长时间开始执行第一次任务
  • timeUnit: 指定提交调度任务的时间单位,默认是毫秒

例如,以下方法每五秒(5000毫秒)调用一次,具有固定的延迟,这意味着该时间段是从每次前一次调用的完成时间开始计算的。

// 每5秒执行一次。时间段是从每次前一次调用的完成时间开始计算
@Scheduled(fixedDelay = 5000)
public void doSomething() {
    // something that should run periodically
}

默认情况下,毫秒将用作固定延迟、固定速率和初始延迟值的时间单位。如果您想使用不同的时间单位,例如秒或分钟,可以通过@Scheduled中的timeUnit属性进行配置。

例如,前面的示例也可以编写如下。

// 每5秒执行一次。时间段是从每次前一次调用的完成时间开始计算
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
    // something that should run periodically
}

如果需要固定速率执行,可以在注释中使用fixedRate属性。以下方法每五秒调用一次(在每次调用的连续开始时间之间测量)。

// 固定速率每5秒执行一次。时间段是从每次前一次调用的开始时间开始计算,即以固定速率执行任务,不关注上次执行完成时间
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
    // something that should run periodically
}

对于固定延迟和固定速率的任务,可以通过指示在第一次执行方法之前等待的时间量来指定初始延迟,如下面的fixedRate示例所示。

// 第一次执行延时1秒,然后以固定速率每5秒执行一次
@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() {
    // something that should run periodically
}

如果简单的周期性调度不够表达,可以提供cron表达式。以下示例仅在工作日运行:

@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
    // something that should run on weekdays only
}

从Spring Framework 4.3开始,任何范围的bean都支持@Scheduled方法。
确保您在运行时没有初始化同一@Scheduled注释类的多个实例,除非您确实希望调度对每个此类实例的回调。与此相关的是,请确保不要在用@Scheduled注释并在容器中注册为常规Spring Bean的类上使用@Configurationable。否则,您将获得两次初始化(一次通过容器,一次通过@Configurationable注解),结果是每个@Scheduled方法被调用两次。

4.3 @Async 注解

您可以在方法上提供@Async注释,以便异步调用该方法。换句话说,调用方在调用时立即返回,而方法的实际执行发生在已提交给Spring TaskExecutor的任务中。在最简单的情况下,可以将注释应用于返回void的方法,如下例所示:

@Async
void doSomething() {
    // this will be run asynchronously
}

与用@Scheduled注释注释的方法不同,这些方法可能需要参数,因为它们是由调用者在运行时以"正常"方式调用的,而不是由容器管理的计划任务调用的。例如,以下代码是@Async注释的合法应用程序:

@Async
void doSomething(String s) {
    // this will be run asynchronously
}

即使有返回值的方法也可以异步调用。但是,此类方法需要具有Future类型的返回值。这仍然提供了异步执行的好处,因此调用者可以在调用Future上的get()之前执行其他任务。以下示例显示如何在返回值的方法上使用@Async:

@Async
Future<String> returnSomething(int i) {
    // this will be run asynchronously
}

@异步方法不仅可以声明常规java.util.concurrent.Future返回类型,还可以声明Spring的org.springframework.util.concurrent.ListenableFuture,或者从Spring 4.2开始,JDK 8的java.util.coccurrent.CompletableFuture,以便与异步任务进行更丰富的交互,并与进一步的处理步骤立即组合。

不能将@Async与生命周期回调(如@PostConstruct)结合使用。要异步初始化Spring Bean,当前必须使用单独的初始化Spring Bean来调用目标上的@Async注释方法,如下例所示:

public class SampleBeanImpl implements SampleBean {

    @Async
    void doSomething() {
        // ...
    }

}

public class SampleBeanInitializer {

    private final SampleBean bean;

    public SampleBeanInitializer(SampleBean bean) {
        this.bean = bean;
    }

    @PostConstruct
    public void initialize() {
        bean.doSomething();
    }

}

4.4 Executor Qualification with @Async

默认情况下,在方法上指定@Async时,所使用的执行器是在启用异步支持时配置的执行器,即,如果使用XML或AsyncConfigurer实现(如果有),则为“注释驱动”元素。但是,当需要指示在执行给定方法时应使用默认值以外的执行器时,可以使用@Async注释的value属性。以下示例显示了如何执行此操作:

@Async("otherExecutor")
void doSomething(String s) {
    // this will be run asynchronously by "otherExecutor"
}

在这种情况下,“otherExecutor”可以是Spring容器中任何Executor Bean的名称,也可以是与任何Executoor关联的限定符的名称(例如,使用元素或Spring的@Qualifier注释指定)。

4.5 @Async异常管理

当@Async方法具有Future类型的返回值时,很容易管理在方法执行期间引发的异常,因为在Future结果上调用get时会引发此异常。然而,对于void返回类型,异常是未捕获的,无法传输。您可以提供AsyncUnaughtExceptionHandler来处理此类异常。以下示例显示了如何执行此操作:

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // handle exception
    }
}

五、cron表达式

5.1 cron表达式详解

所有 Spring cron 表达式都必须符合相同的格式,无论您是在@Scheduled注释、任务、计划任务元素、 或其他地方。 格式正确的 cron 表达式(例如 )由六个空格分隔的时间和日期组成字段,每个字段都有自己的有效值范围:

* * * * * *
┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
│ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN)
│ │ │ │ │ │
* * * * * *

有一些规则适用:

  • 字段可以是星号(*),始终代表“first-last”。对于月日或星期日字段,可以使用问号(?)代替星号。

  • 逗号(,)用于分隔列表中的项目。

  • 用连字符(-)分隔的两个数字表示一系列数字。指定的范围包含在内。

  • 在带/的范围(或*)之后指定数字值在该范围内的间隔。

  • 英文名称也可以用于月份和星期几字段。使用特定日期或月份的前三个字母(大小写无关紧要)。

  • “月日”和“星期日”字段可以包含L字符,其含义不同。

  • 在月日字段中,L代表该月的最后一天。如果后面跟着一个负偏移量(即L-n),则表示该月的第n天到最后一天。

  • 在星期几字段中,L代表一周的最后一天。如果前缀为数字或三个字母的名称(dL或DDDL),则表示当月的最后一天(d或DDD)。

  • “月日”字段可以是nW,它代表一个月中最近的一个工作日。如果n落在星期六,这将产生前一个星期五。如果n在星期天,这将生成后一个星期一,如果n为1并且落在星期天(即:1W代表一个月中的第一个工作日),也会发生这种情况。

  • 如果月日字段为LW,则表示该月的最后一个工作日。

  • 星期几字段可以是d#n(或DDD#n),表示一个月中第n个星期d(或DDD)。

  • 问号(?)只能用在DayofMonth和DayofWeek两个域,由于指定日期(DayofMonth)和指定星期(DayofWeek)存在冲突,所以当指定了日期(DayofMonth)后(包括每天*),星期(DayofWeek)必须使用问号(?),同理,指定星期(DayofWeek)后,日期(DayofMonth)必须使用问号(?)。

以下是一些示例:

Cron 表达式意义
0 0 * * * *每天每个小时之间
*/10 * * * * *每十秒
0 0 8-10 * * *每天8点、9点及10点
0 0 6,19 * * *每天上午 6:00 和晚上 7:00
0 0/30 8-10 * * *每天 8:00、8:30、9:00、9:30、10:00 和 10:30
0 0 9-17 * * MON-FRI工作日朝九晚五的整点
0 0 0 25 DEC ?每个圣诞节午夜
0 0 0 L * *每月最后一天午夜
0 0 0 L-3 * *每月倒数第三天的午夜
0 0 0 * * 5L每月最后一个星期五午夜
0 0 0 * * THUL每月最后一个星期四午夜
0 0 0 1W * *每月第一个工作日的午夜
0 0 0 LW * *每月最后一个工作日的午夜
0 0 0 ? * 5#2每月第二个星期五午夜
0 0 0 ? * MON#1每月第一个星期一午夜

5.2 宏

对于人类来说,诸如0 0***之类的表达式很难解析,因此在出现错误时很难修复。为了提高可读性,Spring支持以下宏,这些宏表示常用的序列。您可以使用这些宏而不是六位数的值,例如:@Scheduled(cron=“@hourly”)。

意义
@yearly(或@annually)每年一次(0 0 0 1 1 *)
@monthly每月一次(0 0 0 1 * *)
@weekly每周一次(0 0 0 * * 0)
@daily(或@midnight)每天一次 (),或0 0 0 * * *
@hourly每小时一次,(0 0 * * * *)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1976796.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

POE服务机器人-快速开始

快速开始 POE与服务机器人部署服务机器人与poe集成迭代你的机器人其他 POE与服务机器人 在本快速入门指南中&#xff0c;我们将使用 Python 构建一个机器人服务器&#xff0c;然后将其与 Poe 集成。一旦您创建了由您的服务器驱动的 Poe 机器人&#xff0c;任何 Poe 用户都可以…

解密XXE漏洞:原理剖析、复现与代码审计实战

在网络安全领域&#xff0c;XML外部实体&#xff08;XXE&#xff09;漏洞因其隐蔽性和危害性而备受关注。随着企业对XML技术的广泛应用&#xff0c;XXE漏洞也逐渐成为攻击者们利用的重点目标。一个看似无害的XML文件&#xff0c;可能成为攻击者入侵系统的利器。因此&#xff0c…

R语言统计分析——描述性统计

参考资料&#xff1a;R语言实战【第2版】 1、整体统计 对于R语言基础安装&#xff0c;可以使用summary()函数来获取描述性统计量。summary()函数提供了最小值、最大值、四分位数、中位数和算术平均数&#xff0c;以及因子向量和逻辑向量的频数统计。 myvars<-c("mpg&…

JRT多维取数据三件套

今天补齐DolerData判断数据是否存在的API&#xff0c;即M的$d。 兜兜转转&#xff0c;经过近十年探索&#xff0c;3年的酝酿&#xff0c;10个月的开发&#xff0c;JRT终于集齐多维取数据三件套。分别是$get,$listget,$data。通过多维取数据的支持&#xff0c;JRT特别适合医疗数…

7.怎么配置一个axios来拦截前后端请求

首先创建一个axios.js文件 导入我们所需要的依赖 import axios from "axios"; import Element from element-ui import router from "./router"; 设置请求头和它的类型和地址 注意先注释这个url,还没有解决跨域问题,不然会出现跨域 // axios.defaults.…

6-5 多输入多输出通道

虽然我们在前面描述了构成每个图像的多个通道和多层卷积层。例如彩色图像具有标准的RGB通道来代表红、绿和蓝。 但是到目前为止&#xff0c;我们仅展示了单个输入和单个输出通道的简化例子。 这使得我们可以将输入、卷积核和输出看作二维张量。 当我们添加通道时&#xff0c;我…

搭建高可用OpenStack(Queen版)集群(一)之架构环境准备

一、搭建高可用OpenStack&#xff08;Queen版&#xff09;集群之架构环境准备 一、架构设计 二、初始化基础环境 1、管理节点创建密钥对&#xff08;方便传输数据&#xff09; 所有控制节点操作 # ssh-keygen #一路回车即可 Generating public/private rsa key pair. Enter f…

MTK Android12 分析system_app允许vendor_mtk_audiohal_prop SELinux 权限问题

本文将尝试分析&#xff0c;在开发 Android 12 MTK 平台时遇到了 vendor_mtk_audiohal_prop 属性相关的 SELinux 权限问题。包括如何修改 SELinux 策略以允许 system_app 设置 vendor_mtk_audiohal_prop 属性。 问题描述 希望允许 system_app 设置 vendor_mtk_audiohal_prop 属…

SpringBoot+Vue图书(图书借阅)管理系统-附项目源码与配套文档

摘 要 本论文阐述了一套先进的图书管理系统的设计与实现&#xff0c;该系统采用Java语言&#xff0c;结合现代Web开发框架和技术&#xff0c;旨在为图书馆提供高效、灵活且用户友好的资源管理解决方案。系统利用Spring Boot框架为核心&#xff0c;整合MyBatis ORM工具&#…

基于 systemc-2.3.1的virtual device 接入 qemu-arm

1&#xff0c;下载systemc-2.3.1 下载网址&#xff1a; SystemC Files $ wget https://www.accellera.org/images/downloads/standards/systemc/systemc-2.3.1.tgz 2&#xff0c;编译安装 systemc-2.3.1 tar zxf systemc-2.3.1.tgz cd systemc-2.3.1/ export CXXg mkdir bu…

PS 2024 百种常用插件下载安装教程【免费使用,先到先得】

文章目录 软件介绍软件下载安装步骤 专栏推荐&#xff1a; 超多精品软件&#xff08;持续更新中…&#xff09; 软件推荐&#xff1a; PS 2024 PR 2024 软件介绍 PS常用插件 此软件整合了市面近百款ps处理插件&#xff0c;可实现&#xff1a;一键制作背景&#xff0c;一键抠图…

linux安装docker(实操教程)

一、安装前准备工作 1.查看服务器操作系统版本 2.查看服务器的操作系统内核版本 3.安装依赖包 yum install -y yum-utils device-mapper-persistent-data lvm2如果不是root用户登陆的系统&#xff0c;需要手动输入sudo -i切换到root帐户 4.设置阿里云docker-ce镜像源 yum-c…

美国失业率大幅上升,增加九月份降息利率的可能性

令人失望的是&#xff0c;美国7月份经济增加了11.4万个工作岗位&#xff0c;低于预期的17.5万个和6月的17.9万个。平均小时工资持续下降&#xff0c;但失业率升至4.3%。美元继续走低&#xff0c;美国国债也在下跌&#xff0c;而黄金则获得了提振。 7月份的非农业支付数据令人失…

ST语言支持包下载安装(VS CODE)

VSCODE是微软提供的代码编辑器&#xff0c;支持非常多的语言。 1、VSCODE下载 2、ST语言支持包 3、ST语言支持包下载 4、ST语言 。。

IndentationError: expected an indented block 深度解析

IndentationError: expected an indented block 深度解析与实战指南 在Python编程中&#xff0c;IndentationError: expected an indented block是一个常见的错误&#xff0c;通常发生在代码块没有正确缩进时。这个错误表明代码中存在格式问题&#xff0c;可能是缩进不一致或缺…

基于PFC和ECN搭建无损RoCE网络的工作流程分析

无损RoCE网络概念 RDMA&#xff08;Remote Direct Memory Access&#xff0c;远程直接内存访问&#xff09;是一种为了解决网络传输中服务器端数据处理延迟而产生的技术。RDMA 将用户应用中的数据直接传入服务器的存储区&#xff0c;通过网络将数据从一个系统快速传输到远程系…

力扣421.数组中两个数的最大异或和

力扣421.数组中两个数的最大异或和 __builtin_clz()&#xff1a;求出mx二进制最高位之后的0的个数 class Solution {public:int findMaximumXOR(vector<int>& nums) {int mx *max_element(nums.begin(),nums.end());//__builtin_clz函数int high_bit mx ? 31 -…

单元测试JUnit

前言&#x1f440;~ 上一章我们介绍了自动化测试工具Selenium&#xff0c;今天讲解单元测试工具JUnit JUnit JUnit的使用 JUnit注解 BeforeAll和AfterAll注解 BeforeEach和AfterEach注解 参数化 方法获取参数&#xff08;动态参数&#xff09; 断言 用例执行顺序 测…

詹妮弗洛佩兹度过一个单身的季节!知情人:双方无法达成妥协,可能几日内将递交离婚申请!

对于詹妮弗洛佩兹来说&#xff0c;这是一个单身的季节&#xff0c;但这似乎并不是她希望从分居的丈夫本阿弗莱克那里得到的。当她在东海岸的汉普顿度过夏天时&#xff0c;这位 51 岁的演员正忙着在西海岸购买价值 2050 万美元的房产。洛佩兹显然认为&#xff0c;尽管他们已经分…

常见的CMS漏洞解析!

PhPMyadmin&#xff1a; 姿势⼀&#xff1a;通过日志文件拿Shell 利用mysql日志文件写shell&#xff0c;这个日志可以在mysql里改变它的存放位置&#xff0c;登录phpmyadmin可以修改 这个存放位置&#xff0c;并且可以修改它的后缀名。所以可以修改成php的后缀名就能获取⼀个…