Spring定時任務關于@EnableScheduling的用法解析
一切的開始(@EnableScheduling)
1.先放上示例代碼
@Configuration
@EnableScheduling
public class MainApplicationBootStrap {
@Bean
public Bride bride(){
return new Bride();
}
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext("com.lc.spring");
Bride bride = annotationConfigApplicationContext.getBean(Bride.class);
System.out.println(bride);
System.in.read();
}
}
//Bride類
public class Bride {
private String name;
private int count;
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd : HH mm ss");
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
//每五秒執(zhí)行一次。
@Scheduled(cron = "0/5 * * * * ?")
public void sayHello2(){
System.out.println(simpleDateFormat.format(new Date()) + ":" + Thread.currentThread().getName() + ": "+ Bride.class.getName() + ": say hello2 " + count++ );
}
}首先看看@EnableScheduling注解里面有什么,再找個類上面spring已經(jīng)很明確得告知,這個注解得作用和相關得拓展方式了,有興趣可以下載看看。這里就不寫了。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)//
@Documented
public @interface EnableScheduling {
}注意@Import注解導入得SchedulingConfiguration。@Import注解是@Configuration一塊使用。這里就不分析在Spring里面怎么解析配置類得了,springboot自動裝配原理注解@EnableAutoConfiguration啟動得關鍵就在于這里。這部分得內容之后再寫。
繼續(xù)看,看看SchedulingConfiguration是什么,里面干了什么事情。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) //spring里面自己用得bean,和用戶自定沒有關系
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}在Spring里面bean有三種角色,
- ROLE_APPLICATION 用戶自定義得bean
- ROLE_SUPPORT 輔助角色
- ROLE_INFRASTRUCTURE 表示完全和用戶得bean沒有關系,表示一個在容器里面自己使用得。
到這里就很肯定了,ScheduledAnnotationBeanPostProcessor就是ScheduleTask實現(xiàn)的重點。
破案了,總結一下
配置類上標注@EnableScheduling注解,@EnableScheduling里面聚合了@Import,@Import最終會導入一個ScheduledAnnotationBeanPostProcessor。
ScheduledAnnotationBeanPostProcessor(ScheduleTask實現(xiàn)的重點)
1. ScheduledAnnotationBeanPostProcessor類圖

下面對ScheduledAnnotationBeanPostProcessor實現(xiàn)的接口逐一說明
- MergedBeanDefinitionPostProcessor是
BeanPostProcessor,在BeanPostProcessor的基礎上增加了postProcessMergedBeanDefinition,這個接口的主要的實現(xiàn)類如下,其中最重要的就是AutowiredAnnotationBeanPostProcessor用于處理Autowired。
//在實例化出來之后,在調用postProcessAfterInitialization之前會調用postProcessMergedBeanDefinition。
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
//初始化之前
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//重點是這個方法。等會看看在ScheduledAnnotationBeanPostProcessor里面干了什么事情。
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}DestructionAwareBeanPostProcessor繼承于BeanPostProcessor,在之前的基礎上面,增加了兩個方法,用于判斷是否需要銷毀,和用于銷毀bean的之前調用。
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
default boolean requiresDestruction(Object bean) {
return true;
}- 實現(xiàn)了很多的rAware接口,Aware接口沒有什么要說的。
SmartInitializingSingleton在spring中bean創(chuàng)建完成之后的回調.
//在所有的bean實例化完成之后,如果bean實現(xiàn)了SmartInitializingSingleton接口,就會調用afterSingletonsInstantiated方法。 void afterSingletonsInstantiated()
ApplicationListener<ContextRefreshedEvent>時間監(jiān)聽,范型里面的ContextRefreshedEvent表示關心的具體事件。- 在整個spring容器創(chuàng)建好,對象也創(chuàng)建好,SmartInitializingSingleton調用之后,一直在最后的最后,會發(fā)布ContextRefreshedEvent事件。
------------------------refresh方法
// Last step: publish corresponding event.
finishRefresh();
------------------------下面是finishRefresh具體的內容。
/**
* Finish the refresh of this context, invoking the LifecycleProcessor's
* onRefresh() method and publishing the
* {@link org.springframework.context.event.ContextRefreshedEvent}.
*/
protected void finishRefresh() {
// Clear context-level resource caches (such as ASM metadata from scanning).
clearResourceCaches();
// Initialize lifecycle processor for this context. 初始化 LifecycleProcessor
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event. 重點就是這個。發(fā)布ContextRefreshedEvent事件,表示活都干完了。
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}DisposableBean在bean銷毀的時候調用,bean銷毀的時候的生命周期是,先調用DestructionAwareBeanPostProcessor#postProcessBeforeDestruction,接著是DisposableBean#destroy方法,后面才是用戶自定義的destroy方法。
2. 針對上面接口幾個重點方法說明
題外話,定時任務大體的實現(xiàn)是什么?
- 想盡方法拿到被@Schedule修飾的方法。
- 將這些方法上的@Schedule標注的解析,保存映射關系。
- 按照觸發(fā)的條件來調度定時任務。
下面會根據(jù)這種邏輯來解析Spring中的定時任務。
1. 想盡方法拿到被@Schedule修飾的方法。
相關方法 postProcessAfterInitialization
首先要知道,postProcessAfterInitialization在SpringBean的生命周期中在那一個環(huán)節(jié),要解視這個問題,要先知道SpringBean的生命周期是什么?那么這個就繁瑣了,生命周期的文檔多的是,找一個看看就可以。簡單的說,就是Spring在初始化完成的最后一步會調用postProcessAfterInitialization。當然,代理對象的創(chuàng)建也是在這里。
那么,下面就對源碼分析分析,源碼不難,看的懂,并且我添加了注釋
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
//得到?jīng)]有被裝飾的最原始的類,就是cglib增強之前的原始的類,并且這里也能說明CGLib是通過繼承來實現(xiàn)增強的
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,//找處了所有的被Scheduled標注的方法
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
}
}
else {
// Non-empty set of methods
annotatedMethods.forEach((method, scheduledMethods) ->
scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}2. 將這些方法上的@Schedule標注的解析,保存映射關系(processScheduled方法解析)
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
//斷言,判單方法是否有參數(shù),如果有參數(shù)就報錯
Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");//這里會判斷 getParameterCount==0,如果不是0的話,就報錯。這也是spring里面定時任務比較雞肋的方法,但是這個我覺得并沒有啥子問題,誰在定時任務執(zhí)行的時候需要傳遞參數(shù)
//判斷這個方法是否是靜態(tài),私有的,
Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
//將需要執(zhí)行的方法封裝成ScheduledMethodRunnable,這個類實現(xiàn)很簡單,就倆屬性,
//target表示bean
//method表示需要執(zhí)行的方法
Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
//標志位,開始都是false,只要找到@Scheduled注解,并且解析到參數(shù),就是false,后面還會對他進行判斷。
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
//存放組裝好的ScheduledTask,
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
//判斷initialDelay
long initialDelay = scheduled.initialDelay();
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
//這里的initialDelayString就是后面的這個樣子 "PT20.345S" -- parses as "20.345 seconds" "PT15M" -- parses as "15 minutes" (where a minute is 60
if (this.embeddedValueResolver != null) {
//這個意味著,這里的initialDelayString是可以寫SPEL表達式的。embeddedValueResolver處理器很常見,會從環(huán)境中替換值
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
if (StringUtils.hasLength(initialDelayString)) {
try {//最后還是得解析成initialDelay。
initialDelay = parseDelayAsLong(initialDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
}
}
}
//檢查cron表達式
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
String zone = scheduled.zone();
if (this.embeddedValueResolver != null) {//這里也能處理
//事實就是這樣,利用embeddedValueResolver來處理值,很巧妙。
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
//解析到cron之后,標志位變?yōu)閠rue
processedSchedule = true;
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();
}
//將cron表達式變?yōu)镃ronTrigger,
//將runnable(ScheduledMethodRunnable)變?yōu)镃ronTask
//將CronTask變?yōu)閟cheduleCronTask,并且將CronTask添加到 registrar的cronTasks屬性去,并還維護了CronTask和scheduleCronTask的映射關系。
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
// At this point we don't need to differentiate between initial delay set or not anymore
if (initialDelay < 0) {
initialDelay = 0;
}
.........省略部分代碼.......這些代碼和上面的cron都是一樣的,不同的是task的類型不同。其余都是一樣的。
// Check whether we had any attribute set 檢查一下,檢查是@schedule里面必須的參數(shù)是否都有
Assert.isTrue(processedSchedule, errorMessage);
//在解析完成之后,將bean和@schedule保存在map里面,map的key是bean,value是set,set存放的是這個bean里面被@schedule標注方法的集合,
// 也就是ScheduledTask集合
//但是這里的加鎖操作,我沒有看懂,不知道這個是干嘛的?
//這里會有并發(fā)的問題嗎?首先他是在postProcessAfterInitialization方法里面起作用的,這個方法在spring解析bean的時候起作用的
//并且spring調用beanPostProcess都是順序調用。不存在并發(fā)問題。
// 所以這里的鎖,我沒有看懂。
synchronized (this.scheduledTasks) {
Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks);
}
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException(
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}問題:
上面代碼中加鎖操作我沒有看懂,有大佬能告知我一下。
說明:
對于上面代碼中幾個重點的類說明
1.ScheduledMethodRunnable
封裝了bean和@Schedule標注的方法,將他倆封裝為一個Runnable。
2.CronTrigger
Trigger表示觸發(fā)器,在@Schedule注解里面每一個類型都對應不同的Trigger。

Trigger里面最核心的方法是`Date nextExecutionTime(TriggerContext triggerContext);` 下一次執(zhí)行的時間,那么對于Cron或者PeriodTigger都是計算下一次執(zhí)行的時間。
3.CronTask
Task表示任務,task不是接口,是一個類。

task中最核心的方法是getRunnable,TiggerTask在它的基礎上增加了getTrigger,CronTask在之前的基礎上增加了getExpression()
4.ScheduledTask
首先,他是final的
有兩個屬性值
private final Task task;//任務
//保留的是ScheduledFuture的引用,ScheduledFuture是scheduledExecutorService提交任務之后的引用對象。
//ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
// }, 0, 0, TimeUnit.SECONDS);
@Nullable
volatile ScheduledFuture<?> future;總的來說,ScheduledTask保留了task提交給scheduledExecutorServic之后的引用對象和task任務。
5.ScheduledTaskRegistrar

- InitializingBean 在調用自定義init方法之前調用
- DisposableBean 上面說了,在調用自定義destroy方法之前調用
- ScheduledTaskHolder,通過這個接口能拿到所有的ScheduledTask
通過ScheduledTaskRegistrar可以注冊ScheduledTask,也能拿到ScheduledTask,所以,之后的重點就看看ScheduledTaskRegistrar的邏輯
到這里,已經(jīng)完成了從bean中檢索Scheduled修飾的方法,并且解析ScheduledTask注解的屬性,轉換為對應的Bean,通過ScheduledTaskRegistrar注冊到ScheduledTaskRegistrar里面。
3. 按照觸發(fā)的條件來調度定時任務。(onApplicationEvent)
前面說過,ScheduledAnnotationBeanPostProcessor實現(xiàn)了ApplicationListener<ContextRefreshedEvent>接口,Spring會在所有的活都干完之后,發(fā)布一個ContextRefreshedEvent事件。重點就在于它。下面看看它里面干了什么事情
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
// Running in an ApplicationContext -> register tasks this late...
// giving other ContextRefreshedEvent listeners a chance to perform
// their work at the same time (e.g. Spring Batch's job registration).
finishRegistration();
}
}finishRegistration重點是它。繼續(xù)沖
protected void scheduleTasks() {
//如果說在spring中沒有 TaskScheduler的實現(xiàn)類,也沒有ScheduledExecutorService的實現(xiàn)類,那就自己默認來一個,
// Executors.newSingleThreadScheduledExecutor(); 核心線程是1,但是最大線程數(shù)是Max。
// 通過ConcurrentTaskScheduler包裝。
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
//下面就是挨個從之前注冊的這些task里面從 ScheduledTaskRegistrar里面維護的雙向映射關系中獲取scheduledTask,通過
// ScheduledTaskRegistrar中的taskScheduled提交任務,將返回的Future對象保存在ScheduledTask引用里面。
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
addScheduledTask(scheduleFixedDelayTask(task));
}
}
}scheduleCronTask看看他里面的的代碼邏輯,別的都大同小異。差別就在于,task的種類可能不一樣,并且提交個taskSchedule的方法可能不一樣
//首先這個方法是ScheduledTaskRegistrar的
@Nullable
public ScheduledTask scheduleCronTask(CronTask task) {
//之前保存在解析schedule的時候保存的CronTask和ScheduledTask之前的引用關系。
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
boolean newTask = false;
//這里肯定是有的,所以,肯定不會從這里走,所以newTask肯定是fasle,那這個方法的返回值肯定是null
if (scheduledTask == null) {
scheduledTask = new ScheduledTask(task);
newTask = true;
}
//提交任務
if (this.taskScheduler != null) {
//提交任務,接下來就看看taskScheduler相關的就好了
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
}
else {
addCronTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}上面的邏輯是提交的整個流程,下面,在來看看TaskScheduler的相關東西。最后的任務肯定都是通過他來執(zhí)行的。重中之重
4. TaskScheduler(重中之重)分析

注意:這個接口默認實現(xiàn)是ThreadPoolTaskScheduler,下面就針對ThreadPoolTaskScheduler和cronTask來做詳細的說明
TaskScheduler 任務的接口里面的詳細的方法,在這里就不展示了,因為太多了,這里就用ScheduledFuture<?> schedule(Runnable task, Trigger trigger);來做詳細的說明

**提示:**默認在Spring里面是不會自動創(chuàng)建的,需要手動的聲明@Bean,這里要注意,他InitializingBean和DisposableBean。這里面肯定有初始化操作和銷毀操作
繼續(xù)看,接著上面看 schedule方法詳情
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
//得到執(zhí)行器
//關于這個執(zhí)行器的參數(shù)的設置可以看ExecutorConfigurationSupport,
ScheduledExecutorService executor = getScheduledExecutor();
try {
//錯誤處理的handle。有兩種handle,一種是出錯了之后打印日志,一種是拋異常
// LoggingErrorHandler
// PropagatingErrorHandler
ErrorHandler errorHandler = this.errorHandler;
if (errorHandler == null) {
errorHandler = TaskUtils.getDefaultErrorHandler(true);
}
//將執(zhí)行器,task,trigger包裝成ReschedulingRunnable,schedule方法通過將這個任務提交給executor,但是這里寫的很巧妙
return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}補充:
1.創(chuàng)建執(zhí)行器線程的相關邏輯要看ExecutorConfigurationSupport
這里列舉線程池配置的參數(shù)
RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
線程的優(yōu)先級是 5
線程名字
return getThreadNamePrefix() + this.threadCount.incrementAndGet();
默認的前綴是
return ClassUtils.getShortName(getClass()) + "-";
corePoolSize=1 maximumPoolSize = MAX_VALUE ttl=0 queue=new DelayedWorkQueue(), //這是xecutor默認的隊列
2.Executor執(zhí)行失敗之后,異常處理默認策略有兩種(主要實現(xiàn)ErrorHandler接口,并且通過set方法也可以設置)
- LoggingErrorHandler(默認)
- PropagatingErrorHandler(打日志,之后報錯)
到這里,還差最后一步,就是怎么按照cron表達式來運行定時任務,并且,到現(xiàn)在為止,沒看到CronTrigger的nextExecutionTime
5. 怎么做調度(ReschedulingRunnable的具體實現(xiàn))

首先他是一個ScheduledFuture。在這里主要看兩個方法run和schedule,這里的實現(xiàn)很巧妙。
- schedule方法?
@Nullable
public ScheduledFuture<?> schedule() {
synchronized (this.triggerContextMonitor) {
//獲取下次任務執(zhí)行的事件
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if (this.scheduledExecutionTime == null) {
return null;
}
//算出需要延遲啟動的事件
long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
//延遲啟動,提交給executor的schedule
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
return this;
}
}- run方法
@Override
public void run() {
Date actualExecutionTime = new Date();
//開始跑任務,這里是通過反射調用的,
//要知道這里的runnable是ScheduledMethodRunnable對象,反射調用就在ScheduledMethodRunnable里面的run方法
//并且這里也有執(zhí)行失敗之后的異常處理
super.run();
Date completionTime = new Date();
synchronized (this.triggerContextMonitor) {
//scheduledExecutionTime這個不是null,因為在調用的時候是先調用schedule方法的,在這個方法里面設置了scheduledExecutionTime(下次執(zhí)行的時間)
Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");
//更新triggerContext,triggerContext是在這個類里面直接new出來的
//上次執(zhí)行時間,上次執(zhí)行的耗費的真正時間,完成任務時間
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
if (!obtainCurrentFuture().isCancelled()) {
//繼續(xù)調用schedule,這就是精髓,今天的重點。繼續(xù)注冊,繼續(xù)循環(huán),
schedule();
}
}
}這兩個方法涉及的確實很精妙,避免了間隔的問題,達到時間絕對的標準。
定時任務還有有一種設計方法
1.將注解里面的cron解析出來,然后維護在一個地方(內存或者redis)
2.有兩個組件,調度器和執(zhí)行器,
- 調度器
- 每間隔多少秒去遍歷所有的任務,看看下次執(zhí)行的時間是否比當前時間小,如果小,那就說明改執(zhí)行了,就將方法傳遞給執(zhí)行器,執(zhí)行器來執(zhí)行任務,調度器還和之前一樣,間隔遍歷所有任務
- 執(zhí)行器
- 執(zhí)行任務
但是這樣存在一個問題,比如間隔10秒,但是任務是需要4秒執(zhí)行一次,這就出現(xiàn)問題了
但是,Spring的這種方式是覺不會出現(xiàn)這樣的問題,并且這種方式還支持取消任務。因為維護了Future引用。
重點
ReschedulingRunnable中的schedule和run方法之前的關系,如果有一個任務每間隔5秒要執(zhí)行一次。
- 開始的時候是調用schedule方法,這個方法里面計算出下次執(zhí)行的時間,并且算出當前時間和下次執(zhí)行時間的差值,然后通過延遲啟動,就能獲取精確的啟動時間。
- 提交之后就會運行run方法,run方法會調用ScheduledMethodRunnable的run方法,ScheduledMethodRunnable里面會通過反射調用方法。
- 運行完成之后,會更新Context,保留這次執(zhí)行的相關信息,然后判斷future是否取消,沒有取消就繼續(xù)調用schedule方法,本次已經(jīng)執(zhí)行完成,下次的任務就繼續(xù)開始了。
到這里,關于Spring中定時任務實現(xiàn)的已經(jīng)結束了。不禁感嘆,這種實現(xiàn)方式真的很精妙。
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- Spring中@EnableScheduling實現(xiàn)定時任務代碼實例
- Spring中的@EnableScheduling定時任務注解
- SpringBoot注解@EnableScheduling定時任務詳細解析
- SpringBoot使用Scheduling實現(xiàn)定時任務的示例代碼
- springboot通過SchedulingConfigurer實現(xiàn)多定時任務注冊及動態(tài)修改執(zhí)行周期(示例詳解)
- springboot項目使用SchedulingConfigurer實現(xiàn)多個定時任務的案例代碼
- SpringBoot使用SchedulingConfigurer實現(xiàn)多個定時任務多機器部署問題(推薦)
- Spring Scheduling本地任務調度設計與實現(xiàn)方式
相關文章
Springboot hibernate envers使用過程詳解
這篇文章主要介紹了Springboot hibernate envers使用過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-06-06
SpringBoot超詳細講解多數(shù)據(jù)源集成
今天分享下SpringBoot多數(shù)據(jù)源集成,我怕麻煩,這里我覺得我的集成也應該是最簡單的,清晰明了2022-05-05
RedisKey的失效監(jiān)聽器KeyExpirationEventMessageListener問題
這篇文章主要介紹了RedisKey的失效監(jiān)聽器KeyExpirationEventMessageListener問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

