Spring中的@Scheduled源碼解析
@Scheduled源碼解析
解析部分
定時(shí)任務(wù)調(diào)度的基礎(chǔ)是ScheduledAnnotationBeanPostProcessor類,這是一個(gè)實(shí)現(xiàn)了BeanPostProcessor接口的后置處理器。
關(guān)于BeanPostProcessor,最主要就是看postProcessBeforeInitialization方法和postProcessAfterInitialization方法做了什么邏輯。 postProcessBeforeInitialization方法沒有實(shí)現(xiàn)邏輯,所以看postProcessAfterInitialization方法的邏輯。
@Override public Object postProcessAfterInitialization(Object bean, String beanName) { /** * 上面省略部分代碼,看下面的關(guān)鍵代碼 */ if (!this.nonAnnotatedClasses.contains(targetClass) && AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) { /** * 這里一長串代碼是為了獲取被@Scheduled和@Schedules注解的方法 */ Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> { Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( method, Scheduled.class, Schedules.class); return (!scheduledMethods.isEmpty() ? scheduledMethods : null); }); //如果沒有被@Scheduled和@Schedules注解的方法,當(dāng)前bean加入到nonAnnotatedClasses集合中,不進(jìn)行處理 if (annotatedMethods.isEmpty()) { this.nonAnnotatedClasses.add(targetClass); if (logger.isTraceEnabled()) { logger.trace("No @Scheduled annotations found on bean class: " + targetClass); } } else { //如果存在被@Scheduled和@Schedules注解的方法,針對(duì)每個(gè)方法調(diào)用processScheduled方法 annotatedMethods.forEach((method, scheduledMethods) -> scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean))); if (logger.isTraceEnabled()) { logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods); } } } return bean; }
根據(jù)以上代碼,總結(jié)出ScheduledAnnotationBeanPostProcessor 類做的事情:
(1)獲取被@Scheduled和@Schedules注解標(biāo)記的方法,若沒有,將此Bean加入到nonAnnotatedClasses集合中。
(2)存在被@Scheduled和@Schedules注解的方法,針對(duì)每個(gè)方法調(diào)用processScheduled方法
所以,接下來就是分析關(guān)鍵在于processScheduled方法做的邏輯
protected void processScheduled(Scheduled scheduled, Method method, Object bean) { try { //將被注解的方法封裝為ScheduledMethodRunnable Runnable runnable = createRunnable(bean, method); boolean processedSchedule = false; String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required"; Set<ScheduledTask> tasks = new LinkedHashSet<>(4); // 解析initialDelay 的值,字符串和整型值不能同時(shí)配置 long initialDelay = scheduled.initialDelay(); String initialDelayString = scheduled.initialDelayString(); if (StringUtils.hasText(initialDelayString)) { Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both"); if (this.embeddedValueResolver != null) { initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString); } if (StringUtils.hasLength(initialDelayString)) { try { initialDelay = parseDelayAsLong(initialDelayString); } catch (RuntimeException ex) { throw new IllegalArgumentException( "Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long"); } } } // Check cron expression String cron = scheduled.cron(); if (StringUtils.hasText(cron)) { String zone = scheduled.zone(); if (this.embeddedValueResolver != null) { cron = this.embeddedValueResolver.resolveStringValue(cron); zone = this.embeddedValueResolver.resolveStringValue(zone); } if (StringUtils.hasLength(cron)) { Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers"); processedSchedule = true; if (!Scheduled.CRON_DISABLED.equals(cron)) { TimeZone timeZone; if (StringUtils.hasText(zone)) { timeZone = StringUtils.parseTimeZoneString(zone); } else { timeZone = TimeZone.getDefault(); } 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; } // Check fixed delay long fixedDelay = scheduled.fixedDelay(); if (fixedDelay >= 0) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay))); } String fixedDelayString = scheduled.fixedDelayString(); if (StringUtils.hasText(fixedDelayString)) { if (this.embeddedValueResolver != null) { fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString); } if (StringUtils.hasLength(fixedDelayString)) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; try { fixedDelay = parseDelayAsLong(fixedDelayString); } catch (RuntimeException ex) { throw new IllegalArgumentException( "Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long"); } tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay))); } } // Check fixed rate long fixedRate = scheduled.fixedRate(); if (fixedRate >= 0) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay))); } String fixedRateString = scheduled.fixedRateString(); if (StringUtils.hasText(fixedRateString)) { if (this.embeddedValueResolver != null) { fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString); } if (StringUtils.hasLength(fixedRateString)) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; try { fixedRate = parseDelayAsLong(fixedRateString); } catch (RuntimeException ex) { throw new IllegalArgumentException( "Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long"); } tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay))); } } // Check whether we had any attribute set Assert.isTrue(processedSchedule, errorMessage); // Finally register the scheduled tasks 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()); } }
根據(jù)以上代碼,大致邏輯如下:
(1)解析initialDelay的值
(2)根據(jù)@Scheduled注解的屬性配置,分別將此bean的被注解//方法封裝為CronTask,F(xiàn)ixedDelayTask,F(xiàn)ixedRateTask
(3)this.registrar 根據(jù)封裝的任務(wù)類型使用對(duì)應(yīng)的調(diào)度方法scheduleXXX 若此時(shí)taskScheduler局部變量還沒有初始化完成,那么將會(huì)加入到一個(gè)臨時(shí)的集合存起來,不進(jìn)行調(diào)度,這個(gè)taskScheduler可以看做是一個(gè)調(diào)度任務(wù)專用的線程池
(4)調(diào)度方法返回的結(jié)果加入到tasks集合中
(5)然后按照bean分類,放入scheduledTasks集合(以bean為key的Map集合)
其中@Scheduled注解 的限制如下:
(1)cron表達(dá)式不能與initialDelay,fixedDelay,fixedRate一起配置
(2)fixedDelay不能與cron同時(shí)設(shè)置
(3)fixedRate不能與cron 同時(shí)配置
(4)fixedDelay 和fixedRate不能同時(shí)配置
至此postProcessAfterInitialization方法執(zhí)行完成。 粗略總結(jié)一下,這個(gè)方法就是把@Scheduled注解的方法解析出來,然后轉(zhuǎn)化為ScheduledTask,這大概是代表了一個(gè)定時(shí)任務(wù)的對(duì)象,然后再按bean分組存放到一個(gè)Map集合中。
執(zhí)行部分
經(jīng)過驗(yàn)證,其實(shí)上面在執(zhí)行postProcessAfterInitialization方法,taskScheduler還是為null的,也就是說,各個(gè)定時(shí)任務(wù)實(shí)際上還是沒辦法開始調(diào)度執(zhí)行。 舉個(gè)例子:
@Nullable public ScheduledTask scheduleFixedRateTask(FixedRateTask task) { ScheduledTask scheduledTask = this.unresolvedTasks.remove(task); boolean newTask = false; if (scheduledTask == null) { scheduledTask = new ScheduledTask(task); newTask = true; } if (this.taskScheduler != null) { if (task.getInitialDelay() > 0) { Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay()); scheduledTask.future = this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval()); } else { scheduledTask.future = this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval()); } } else { addFixedRateTask(task); this.unresolvedTasks.put(task, scheduledTask); } return (newTask ? scheduledTask : null); }
此時(shí)由于taskScheduler 為null,因此沒有執(zhí)行this.taskScheduler.scheduleAtFixedRate方法,而是調(diào)用了addFixedRateTask(task)。(經(jīng)過測試,就算自定義了taskScheduler,也不會(huì)在這時(shí)候賦值的) 那上面的this.taskScheduler.scheduleAtFixedRate方法 在什么執(zhí)行?帶著這個(gè)疑問,調(diào)試打點(diǎn),最終發(fā)現(xiàn)在onApplicationEvent方法中它才會(huì)執(zhí)行調(diào)度,此時(shí)taskScheduler不為空。
ScheduledAnnotationBeanPostProcessor 類實(shí)現(xiàn)了ApplicationListener接口,監(jiān)聽ContextRefreshedEvent 事件。根據(jù)以前學(xué)習(xí)的Spirng加載流程,ContextRefreshedEvent 事件是Spring容器加載完成之后,執(zhí)行finishRefesh方法時(shí)發(fā)布的。 在監(jiān)聽方法里面主要執(zhí)行了finishRegistration()方法
private void finishRegistration() { //片段1 if (this.beanFactory instanceof ListableBeanFactory) { Map<String, SchedulingConfigurer> beans = ((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class); List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values()); AnnotationAwareOrderComparator.sort(configurers); for (SchedulingConfigurer configurer : configurers) { configurer.configureTasks(this.registrar); } } //省略若干代碼.... this.registrar.afterPropertiesSet(); }
有一個(gè)地方,個(gè)人覺得值得了解的: 片段1:自定義調(diào)度線程池時(shí)實(shí)現(xiàn)了SchedulingConfigurer接口 的configureTasks方法,這個(gè)方法就是在片段1執(zhí)行的。
然后之后比較重要的。主要看this.registrar.afterPropertiesSet方法 this.registrar.afterPropertiesSet方法里面調(diào)用了scheduleTasks()方法
protected void scheduleTasks() { //片段1 if (this.taskScheduler == null) { this.localExecutor = Executors.newSingleThreadScheduledExecutor(); this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); } //片段2 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)); } } }
片段1:this.taskScheduler 如果為null,則使用Executors.newSingleThreadScheduledExecutor()。
如果是自定義線程池,則不會(huì)執(zhí)行,因?yàn)榇藭r(shí)已經(jīng)賦值了。
片段2:根據(jù)不同的定時(shí)任務(wù)類型,分別調(diào)用不同的調(diào)度API
這里的this.cronTasks,this.fixedRateTasks,this.fixedDelayTasks 就是上面執(zhí)行processScheduled方法時(shí),因?yàn)閠his.taskScheduler 為null而把定時(shí)任務(wù)臨時(shí)存放的地方。 因?yàn)楝F(xiàn)在已經(jīng)有this.taskScheduler ,因此正式將它們加入調(diào)度,并放入scheduledTasks 集合中(已經(jīng)參與調(diào)度的不會(huì)重復(fù)加入)。
小結(jié)
(1)從這個(gè)源碼分析,可以知道通過實(shí)現(xiàn)SchedulingConfigurer接口自定義調(diào)度線程池的配置
(2)@Scheduled注解 的限制,不能同時(shí)配置多種任務(wù)類型
到此這篇關(guān)于Spring中的@Scheduled源碼解析的文章就介紹到這了,更多相關(guān)@Scheduled源碼解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot中@Scheduled()注解以及cron表達(dá)式詳解
- Spring 定時(shí)任務(wù)@Scheduled 注解中的 Cron 表達(dá)式詳解
- spring?boot?使用?@Scheduled?注解和?TaskScheduler?接口實(shí)現(xiàn)定時(shí)任務(wù)
- SpringBoot中定時(shí)任務(wù)@Scheduled的多線程使用詳解
- SpringBoot通過@Scheduled實(shí)現(xiàn)定時(shí)任務(wù)及單線程運(yùn)行問題解決
- SpringBoot中定時(shí)任務(wù)@Scheduled注解的使用解讀
相關(guān)文章
Spring中的@CrossOrigin注冊(cè)處理方法源碼解析
這篇文章主要介紹了Spring中的@CrossOrigin注冊(cè)處理方法源碼解析,@CrossOrigin是基于@RequestMapping,@RequestMapping注釋方法掃描注冊(cè)的起點(diǎn)是equestMappingHandlerMapping.afterPropertiesSet(),需要的朋友可以參考下2023-12-12使用jvm sandbox對(duì)三層嵌套類型的改造示例
這篇文章主要為大家介紹了使用jvm sandbox對(duì)三層嵌套類型的改造示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08基于Java實(shí)現(xiàn)簡易的七星彩號(hào)碼生成器
七星彩是中國體育彩票的一種玩法,由中國國家體育總局體育彩票管理中心統(tǒng)一發(fā)行。本文為大家準(zhǔn)備了一個(gè)七星彩號(hào)碼生成器Java工具類,感興趣的可以了解一下2022-08-08詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)
本篇文章主要介紹了詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03MyBatis-Plus實(shí)現(xiàn)多表聯(lián)查的方法實(shí)戰(zhàn)
這篇文章主要給大家介紹了關(guān)于MyBatis-Plus實(shí)現(xiàn)多表聯(lián)查的方法,MyBatis Plus是一款針對(duì)MyBatis框架的增強(qiáng)工具,它提供了很多方便的方法來實(shí)現(xiàn)多表聯(lián)查,需要的朋友可以參考下2023-07-07