【SpringBoot】 定时任务之任务执行和调度及使用指南
【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
ThreadPoolExecutor
和ThreadPoolTaskExecutor
很多人容易把这两个搞混。
我们实际开发中更多的是使用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 {@Beanprivate ThreadPoolTaskExecutor execThreadPoolTaskExecutor() {ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();pool.setCorePoolSize(10); // 核心线程数pool.setMaxPoolSize(50); // 最大线程数pool.setQueueCapacity(500); // 等待队列sizepool.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*/@NullableScheduledFuture<?> 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接口的两种实现CronTrigger
和PeriodicTrigger
。
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 {@Asyncvoid doSomething() {// ...}}public class SampleBeanInitializer {private final SampleBean bean;public SampleBeanInitializer(SampleBean bean) {this.bean = bean;}@PostConstructpublic 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 {@Overridepublic 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 * * * *) |
相关文章:

【SpringBoot】 定时任务之任务执行和调度及使用指南
【SpringBoot】 定时任务之任务执行和调度及使用指南 Spring框架分别通过TaskExecutor和TaskScheduler接口为任务的异步执行和调度提供了抽象。Spring还提供了支持应用程序服务器环境中的线程池或CommonJ委托的那些接口的实现。最终,在公共接口后面使用这些实现&…...

理解 Objective-C 中 +load 方法的执行顺序
在 Objective-C 中,load 方法是在类或分类(category)被加载到内存时调用的。它的执行顺序非常严格,并且在应用启动过程中可能会导致一些令人困惑的行为。理解 load 方法的执行顺序对调试和控制应用的初始化过程非常重要。 load 方…...

切面条问题算法的详解
切面条问题是一个经典的动态规划问题,也称为切钢条问题。问题描述为:给定一根长度为n的钢条和一个价格表P[i],表示长度为i的钢条的价格。求解如何切割钢条使得收益最大。 解决这个问题的关键是找到一个最优子结构和递推关系。 首先…...

JNDI注入
🎼个人主页:金灰 😎作者简介:一名简单的大一学生;易编橙终身成长社群的嘉宾.✨ 专注网络空间安全服务,期待与您的交流分享~ 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持!❤️ 🍊易编橙终身成长社群&#…...

SQL Server数据库文件过大而无法直接导出解决方案
目录 1. 使用分割备份 (Split Backup) 2. 使用文件和文件组备份 (File and Filegroup Backup) 3. 使用压缩备份 (Compressed Backup) 4. 逻辑备份 (BCP工具) 5. 使用导出工具 (SQL Server Management Studio) 6. 部分备份 (Partial Backup) 7. 使用第三方工具 1. 使用分割…...

学习日志8.4--DHCP攻击防范
目录 DHCP饿死攻击 DHCP Sever仿冒攻击 DHCP攻击防范 DHCP动态主机配置协议,是给主机提供自动获取IP地址等配置信息的服务。在主机对DHCP服务器发送DHCP Discover请求之后,服务器回复offer,主机再回复request,最后服务器回复AC…...

解决多个Jenkins Master实例共享Jenkins_home目录的问题(加锁解锁机制)
在Jenkins的持续集成和持续部署(CI/CD)环境中,JENKINS_HOME目录扮演着至关重要的角色。它存储了Jenkins的配置、插件、作业历史记录等核心数据。然而,在某些场景下,我们可能面临多个Jenkins master实例需要共享同一个J…...

postgresql array 反向截取
postgresql array 反向截取 array_to_string((string_to_array(REPLACE(delcell.小区网管名称,‘‘,’-‘),’-‘))[:array_length(string_to_array(REPLACE(delcell.小区网管名称,’’,‘-’),‘-’),1)-1],‘-’) as 基站名称 在PostgreSQL中,如果你想要对数组进…...

最新口型同步技术EchoMimic部署
EchoMimic是由蚂蚁集团推出的一个 AI 驱动的口型同步技术项目,能够通过人像面部特征和音频来帮助人物“对口型”,生成逼真的动态肖像视频。 EchoMimic的技术亮点在于其创新的动画生成方法,它不仅能够通过音频和面部关键点单独驱动图像动画&a…...

程序设计基础(c语言)_补充_1
1、编程应用双层循环输出九九乘法表 #include <stdio.h> #include <stdlib.h> int main() {int i,j;for(i1;i<9;i){for(j1;j<i;j)if(ji)printf("%d*%d%d",j,i,j*i);elseprintf("%d*%d%-2d ",j,i,j*i);printf("\n");}return 0…...

8.4 day bug
bug1 忘记给css变量加var 复制代码到通义千问,解决 bug2 这不是我的bug,是freecodecamp的bug 题目中“ 将 --building-color2 变量的颜色更改为 #000” “ 应改为” 将 #000 变量的颜色更改为 --building-color2 “ bug3 又忘记加var(–xxx) 还去问…...

【Material-UI】Autocomplete中的禁用选项:Disabled options
文章目录 一、简介二、基本用法三、进阶用法1. 动态禁用2. 提示禁用原因3. 复杂的禁用条件 四、最佳实践1. 一致性2. 提供反馈3. 优化性能 五、总结 Material-UI的Autocomplete组件提供了丰富的功能,包括禁用特定选项的能力。这一特性对于限制用户选择、提供更好的用…...

Pytest测试报告生成专题
在 pytest 中,你可以使用多个选项生成不同格式的测试报告。以下是几种常用的生成测试报告的方法: 1. 生成简单的测试结果文件 你可以使用 pytest 的 --junitxml 选项生成一个 XML 格式的测试报告,这个报告可以与 CI/CD 工具集成。 pytest --junitxml=report.xml这将在当前…...

QT 笔记
HTTPS SSL配置 下载配置 子父对象 QTimer *timer new QTimer; // QTimer inherits QObject timer->inherits("QTimer"); // returns true timer->inherits("QObject"); // returns true timer->inherits("QAbst…...

【redis 第七篇章】动态字符串
一、概述 string 类型底层实现的简单动态字符串 sds,是可以修改的字符串。它采用预分配冗余空间的方式来减少内存的频繁分配。 二、SDS动态字符串 动态字符串 是以 \0 为分隔符。最大容量 是 redis 主动分配的一块内存空间,实际存储内容 是具体的存的数…...

rk3588 部署yolov8.rknn
本文从步骤来记录在rk3588芯片上部署yolov8模型 主机:windows10 VMware Workstation 16 Pro 硬件:RK3588 EVB板 模型: RK3588.rknn 软件开发环境: c cmake step1: 主机上执行: 将rknn_model_zoo 工程文件下载…...

【正点原子i.MX93开发板试用连载体验】中文提示词的训练
本文首发于电子发烧友论坛:【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! 好久没有更新了,今天再来更新一下。 我们用前面提到的录音工具录制了自己的中文语音&#…...

WordPress资源下载类主题 CeoMax-Pro_v7.6绕授权开心版
CeoMax-Pro强大的功能 在不久的将来Ta能实现你一切幻想!我们也在为此而不断努力。适用于资源站、下载站、交易站、素材站、源码站、课程站、cms等等等等,Ta 为追求极致的你而生。多风格多样式多类型多行业多功能 源码下载:ceomax-pro7.6.zip…...

使用GCC编译Notepad++的插件
Notepad的本体1是支持使用MSVC和GCC编译的2,但是Notepad插件的官方文档3里却只给出了MSVC的编译指南4。 网上也没有找到相关的讨论,所以我尝试在 Windows 上使用 MinGW,基于 GCC-8.1.0 的 posix-sjlj 线程版本5,研究一下怎么编译…...

技术周总结 2024.07.29 ~ 08.04周日(MyBatis, 极限编程)
文章目录 一、08.01 周四1.1)mybatis的 xml文件中的 ${var} 和 #{var}的区别? 二、08.03 周六2.1)极限编程核心价值观核心实践实施极限编程的好处极限编程的挑战适用场景 三、08.04 周日3.1)《计算机信息系统安全保护等级划分准则…...

C语言调试宏全面总结(六大板块)
C语言调试宏进阶篇:实用指南与案例解析C语言调试宏高级技巧与最佳实践C语言调试宏的深度探索与性能考量C语言调试宏在嵌入式系统中的应用与挑战C语言调试宏在多线程环境中的应用与策略C语言调试宏在并发编程中的高级应用 C语言调试宏进阶篇:实用指南与案…...

unity万向锁代数法解释
unity的矩阵旋转乘法顺序是yxz 旋转x的90度的矩阵: 1 0 0 0 0 -1 0 1 0旋转y和z的矩阵假设角度为y和z,矩阵略不写了 按顺序乘完yxz之后结果是 cos(y-z) sin(y-z) 0 0 0 -1 -sin(y-z) cos(y-z) 0这个结果和Rx(pi/2) *Rz(某个角度)的结果是一个形式,Rx和…...

stm32入门学习10-I2C和陀螺仪模块
(一)I2C通信 (1)通信方式 I2C是一种同步半双工的通信方式,同步指的是通信双方时钟为一个时钟,半双工指的是在同一时间只能进行接收数据或发送数据,其有一条时钟线(SCL)…...

GDB常用指令
GDB调试:GDB调试的是可执行文件,在gcc编译时加入-g参数,告诉gcc在编译时加入调试信息,这样gdb才能调试这个被编译的文件。此外还会加上-Wall参数尽量显示所有警告信息。 GDB命令格式: 1、start:程序在第一…...

Nginx 高级 扩容与高效
Nginx高级 第一部分:扩容 通过扩容提升整体吞吐量 1.单机垂直扩容:硬件资源增加 云服务资源增加 整机:IBM、浪潮、DELL、HP等 CPU/主板:更新到主流 网卡:10G/40G网卡 磁盘:SAS(SCSI) HDD(机械…...

pythonflaskMYSQL自驾游搜索系统32127-计算机毕业设计项目选题推荐(附源码)
目 录 摘要 1 绪论 1.1研究背景 1.2爬虫技术 1.3flask框架介绍 2 1.4论文结构与章节安排 3 2 自驾游搜索系统分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1数据增加流程 5 2.3.2数据修改流程 5 2.3.3数据删除流程 5 2.3 系统功能分析 5 2.3.1 功能性分析 6 2.3.2 非功…...

C++ vector的基本使用(待补全)
std::vector 是C标准模板库(STL)中的一个非常重要的容器类,它提供了一种动态数组的功能。能够存储相同类型的元素序列,并且可以自动管理存储空间的大小,以适应序列大小变化,处理元素集合的时候很灵活 1. vector的定义 构造函数声…...

Java 属性拷贝 三种实现方式
第一种 List<OrederPayCustomer> orederPayCustomerList this.list(queryWrapper); List<CustomerResp>customerRespListnew ArrayList<>();for (OrederPayCustomer orederPayCustomer : orederPayCustomerList) {CustomerResp customerResp new Custome…...

Java-变量,运算符,输入与输出
目录 一,语法基础 1.基本Java程序 2.语法基础 2.1 变量 2.2 常量限制(fiinal)类比C中的const 2.3 类型转化 2.4 运算符 2.5 表达式 2.5 输入与输出 2.5.1 输入 2.5.2 输出 一,语法基础 1.基本Java程序 public class Main{public static void…...

五、一个quad同时支持pcie和sfp两种高速接口的ref时钟配置
项目描述 上位机将截图数据通过 XDMA 写入到 FPGA 侧的 DDR 内存区域 1 中通过 axi_lite 接口给 axi_read_start 信号,通知 AXI_read 模块启动读取数据,然后通过 GTP TX 模块发送出去。经过光纤回环,GTP RX 端接收到数据,送给 AX…...