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啟動得關鍵就在于這里。這部分得內(nèi)容之后再寫。
繼續(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。
//在實例化出來之后,在調(diào)用postProcessAfterInitialization之前會調(diào)用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的之前調(diào)用。
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException; default boolean requiresDestruction(Object bean) { return true; }
- 實現(xiàn)了很多的rAware接口,Aware接口沒有什么要說的。
SmartInitializingSingleton
在spring中bean創(chuàng)建完成之后的回調(diào).
//在所有的bean實例化完成之后,如果bean實現(xiàn)了SmartInitializingSingleton接口,就會調(diào)用afterSingletonsInstantiated方法。 void afterSingletonsInstantiated()
ApplicationListener<ContextRefreshedEvent>
時間監(jiān)聽,范型里面的ContextRefreshedEvent表示關心的具體事件。- 在整個spring容器創(chuàng)建好,對象也創(chuàng)建好,SmartInitializingSingleton調(diào)用之后,一直在最后的最后,會發(fā)布ContextRefreshedEvent事件。
------------------------refresh方法 // Last step: publish corresponding event. finishRefresh(); ------------------------下面是finishRefresh具體的內(nèi)容。 /** * 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銷毀的時候調(diào)用,bean銷毀的時候的生命周期是,先調(diào)用DestructionAwareBeanPostProcessor#postProcessBeforeDestruction,接著是DisposableBean#destroy方法,后面才是用戶自定義的destroy方法。
2. 針對上面接口幾個重點方法說明
題外話,定時任務大體的實現(xiàn)是什么?
- 想盡方法拿到被@Schedule修飾的方法。
- 將這些方法上的@Schedule標注的解析,保存映射關系。
- 按照觸發(fā)的條件來調(diào)度定時任務。
下面會根據(jù)這種邏輯來解析Spring中的定時任務。
1. 想盡方法拿到被@Schedule修飾的方法。
相關方法 postProcessAfterInitialization
首先要知道,postProcessAfterInitialization在SpringBean的生命周期中在那一個環(huán)節(jié),要解視這個問題,要先知道SpringBean的生命周期是什么?那么這個就繁瑣了,生命周期的文檔多的是,找一個看看就可以。簡單的說,就是Spring在初始化完成的最后一步會調(diào)用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調(diào)用beanPostProcess都是順序調(diào)用。不存在并發(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 在調(diào)用自定義init方法之前調(diào)用
- DisposableBean 上面說了,在調(diào)用自定義destroy方法之前調(diào)用
- ScheduledTaskHolder,通過這個接口能拿到所有的ScheduledTask
通過ScheduledTaskRegistrar可以注冊ScheduledTask,也能拿到ScheduledTask,所以,之后的重點就看看ScheduledTaskRegistrar
的邏輯
到這里,已經(jīng)完成了從bean中檢索Scheduled修飾的方法,并且解析ScheduledTask注解的屬性,轉(zhuǎn)換為對應的Bean,通過ScheduledTaskRegistrar注冊到ScheduledTaskRegistrar里面。
3. 按照觸發(fā)的條件來調(diào)度定時任務。(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. 怎么做調(diào)度(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(); //開始跑任務,這里是通過反射調(diào)用的, //要知道這里的runnable是ScheduledMethodRunnable對象,反射調(diào)用就在ScheduledMethodRunnable里面的run方法 //并且這里也有執(zhí)行失敗之后的異常處理 super.run(); Date completionTime = new Date(); synchronized (this.triggerContextMonitor) { //scheduledExecutionTime這個不是null,因為在調(diào)用的時候是先調(diào)用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ù)調(diào)用schedule,這就是精髓,今天的重點。繼續(xù)注冊,繼續(xù)循環(huán), schedule(); } } }
這兩個方法涉及的確實很精妙,避免了間隔的問題,達到時間絕對的標準。
定時任務還有有一種設計方法
1.將注解里面的cron解析出來,然后維護在一個地方(內(nèi)存或者redis)
2.有兩個組件,調(diào)度器和執(zhí)行器,
- 調(diào)度器
- 每間隔多少秒去遍歷所有的任務,看看下次執(zhí)行的時間是否比當前時間小,如果小,那就說明改執(zhí)行了,就將方法傳遞給執(zhí)行器,執(zhí)行器來執(zhí)行任務,調(diào)度器還和之前一樣,間隔遍歷所有任務
- 執(zhí)行器
- 執(zhí)行任務
但是這樣存在一個問題,比如間隔10秒,但是任務是需要4秒執(zhí)行一次,這就出現(xiàn)問題了
但是,Spring的這種方式是覺不會出現(xiàn)這樣的問題,并且這種方式還支持取消任務。因為維護了Future引用。
重點
ReschedulingRunnable中的schedule和run方法之前的關系,如果有一個任務每間隔5秒要執(zhí)行一次。
- 開始的時候是調(diào)用schedule方法,這個方法里面計算出下次執(zhí)行的時間,并且算出當前時間和下次執(zhí)行時間的差值,然后通過延遲啟動,就能獲取精確的啟動時間。
- 提交之后就會運行run方法,run方法會調(diào)用ScheduledMethodRunnable的run方法,ScheduledMethodRunnable里面會通過反射調(diào)用方法。
- 運行完成之后,會更新Context,保留這次執(zhí)行的相關信息,然后判斷future是否取消,沒有取消就繼續(xù)調(diào)用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本地任務調(diào)度設計與實現(xiàn)方式
相關文章
Springboot hibernate envers使用過程詳解
這篇文章主要介紹了Springboot hibernate envers使用過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-06-06SpringBoot超詳細講解多數(shù)據(jù)源集成
今天分享下SpringBoot多數(shù)據(jù)源集成,我怕麻煩,這里我覺得我的集成也應該是最簡單的,清晰明了2022-05-05RedisKey的失效監(jiān)聽器KeyExpirationEventMessageListener問題
這篇文章主要介紹了RedisKey的失效監(jiān)聽器KeyExpirationEventMessageListener問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05