description: "在 Spring 項目中响牛,@Scheduled 注解配置的計劃任務(Scheduled Tasks)可能會出現(xiàn)執(zhí)行多次的情況"
date: 2024.11.03 10:34
categories:
- Spring
tags: [Spring]
keywords: Spring, @Scheduled, extends, 重復執(zhí)行
原文地址:https://wyiyi.github.io/amber/2024/11/01/Scheduled/
在Spring
項目中饶米,@Scheduled
注解配置的計劃任務(Scheduled Tasks
)可能會出現(xiàn)執(zhí)行多次的情況轨功,尤其是在以下場景中:
- 一個父類定義了
@Scheduled
注解的方法岔冀,且被多個子類繼承。 - 父類或子類被
Spring
容器錯誤地實例化為多個Bean
實例。
本文將針對該特定場景,剖析導致計劃任務重復執(zhí)行的原因蛤奥,并針對性地提出解決措施。
一僚稿、現(xiàn)象描述
在Spring
項目中凡桥,我們定義了一個計劃任務類ScheduledTaskParent
,以及兩個繼承該類的子類FirstChild
和SecondChild
蚀同。
@Component
public class ScheduledTaskParent {
@Scheduled(fixedRate = 5000)
public void performTask() {
System.out.println("ScheduledTaskParent 執(zhí)行計劃任務");
}
}
@Component
public class FirstChild extends ScheduledTaskParent {
@Scheduled(fixedRate = 5000)
public void firstTask() {
System.out.println("FirstChild 執(zhí)行特定的操作");
}
}
@Component
public class SecondChild extends ScheduledTaskParent {
@Scheduled(fixedRate = 5000)
public void secondTak() {
System.out.println("SecondChild 執(zhí)行計劃任務");
}
}
當應用啟動后缅刽,發(fā)現(xiàn)ScheduledTaskParent
中的計劃任務被執(zhí)行了多次,具體表現(xiàn)為每個子類實例都執(zhí)行了父類的計劃任務蠢络,導致執(zhí)行次數(shù)為子類數(shù)目加1衰猛。
FirstChild 執(zhí)行特定的操作
ScheduledTaskParent 執(zhí)行計劃任務
ScheduledTaskParent 執(zhí)行計劃任務
SecondChild 執(zhí)行計劃任務
ScheduledTaskParent 執(zhí)行計劃任務
二、原因分析
As of Spring Framework 4.3, @Scheduled methods are supported on beans of any scope.
Make sure that you are not initializing multiple instances of the same @Scheduled annotation class at runtime, unless you do want to schedule callbacks to each such instance. Related to this, make sure that you do not use @Configurable on bean classes that are annotated with @Scheduled and registered as regular Spring beans with the container. Otherwise, you would get double initialization (once through the container and once through the @Configurable aspect), with the consequence of each @Scheduled method being invoked twice.
Spring 官方文檔 提到刹孔,從Spring Framework 4.3
開始啡省,@Scheduled
注解支持任何作用域的bean
。但是髓霞,文檔也警告卦睹,不應該在運行時初始化同一@Scheduled
注解類的多個實例,除非希望每個實例都調(diào)度回調(diào)方库。
在例子中结序,ScheduledTaskParent
被標記為@Component
,因此Spring
容器會為其創(chuàng)建一個bean
纵潦。由于FirstChild
和SecondChild
都繼承了ScheduledTaskParent
徐鹤,并且它們也被標記為@Component
,Spring
容器為每個子類也創(chuàng)建了bean
邀层。每個bean
都包含performTask
方法上的@Scheduled
注解返敬,因此每個bean
都會觸發(fā)該任務的調(diào)度。
這就是為什么performTask
被執(zhí)行了三次:
一次來自ScheduledTaskParent
的bean
被济,一次來自FirstChild
的bean
救赐,還有一次來自SecondChild
的bean
。
三、解決方案
為了防止計劃任務在子類中被重復執(zhí)行经磅,我們可以在父類中定義一個抽象方法泌绣,并在子類中實現(xiàn)具體的計劃任務。如下所示:
public abstract class ScheduledTaskParent {
public abstract void performTask();
}
@Component
public class FirstChild extends ScheduledTaskParent {
@Scheduled(fixedRate = 5000)
@Override
public void performTask() {
System.out.println("FirstChild 執(zhí)行特定的操作");
}
}
@Component
public class SecondChild extends ScheduledTaskParent {
@Scheduled(fixedRate = 5000)
@Override
public void performTask() {
System.out.println("SecondChild 執(zhí)行計劃任務");
}
}
通過這種方式预厌,確保每個計劃任務只被調(diào)度一次阿迈,即使有多個子類繼承了父類。