Spring中@EnableScheduling注解的工作原理詳解
概述
- 開(kāi)發(fā)人員使用注解 @EnableScheduling;
- 注解@EnableScheduling導(dǎo)入SchedulingConfiguration;
- SchedulingConfiguration定義基礎(chǔ)設(shè)施bean ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor;
- ScheduledAnnotationBeanPostProcessor在容器啟動(dòng)時(shí)做如下事情
- 登記所有使用@Scheduled注解的bean方法到一個(gè)ScheduledTaskRegistrar,供調(diào)度任務(wù)執(zhí)行器TaskScheduler執(zhí)行。
- 為ScheduledTaskRegistrar指定任務(wù)執(zhí)行器TaskScheduler,該任務(wù)執(zhí)行器來(lái)自容器中的bean TaskScheduler/ScheduledExecutorService(如果不指定,ScheduledTaskRegistrar自己會(huì)本地創(chuàng)建一個(gè)ConcurrentTaskScheduler)
- 告訴ScheduledTaskRegistrar將所注冊(cè)的調(diào)度任務(wù),也就是使用@Scheduled注解的bean方法,調(diào)度到任務(wù)執(zhí)行器TaskScheduler執(zhí)行。
詳細(xì)分析
開(kāi)發(fā)人員注解@EnableScheduling
@EnableScheduling // <== @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
@EnableScheduling注解導(dǎo)入配置@SchedulingConfiguration
@Import(SchedulingConfiguration.class) // <== public @interface EnableScheduling { }
SchedulingConfiguration 定義bean scheduledAnnotationProcessor
@Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class SchedulingConfiguration { // Bean 名稱使用 : // org.springframework.context.annotation.internalScheduledAnnotationProcessor @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 定義為基礎(chǔ)設(shè)施bean public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() { return new ScheduledAnnotationBeanPostProcessor(); } }
ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization檢測(cè)處理每個(gè)@Scheduled注解的方法 ScheduledAnnotationBeanPostProcessor實(shí)現(xiàn)了DestructionAwareBeanPostProcessor,BeanPostProcessor等接口。作為一個(gè)BeanPostProcessor,ScheduledAnnotationBeanPostProcessor會(huì)針對(duì)每個(gè)bean的創(chuàng)建,在bean生命周期方法#postProcessAfterInitialization中,掃描該bean中使用了注解@Scheduled的方法,
@Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler || bean instanceof ScheduledExecutorService) { // Ignore AOP infrastructure such as scoped proxies. return bean; } Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); // this.nonAnnotatedClasses 是一個(gè)緩存,用于記錄處理過(guò)程中所發(fā)現(xiàn)的不包含任何被@Scheduled注解的方法的類 if (!this.nonAnnotatedClasses.contains(targetClass)) { // 獲取類 targetClass 上所有使用注解 @Scheduled 的方法 // 注意 : 某個(gè)方法上可能同時(shí)使用多個(gè)注解 @Scheduled ,所以以下 annotatedMethods 的每個(gè) Entry 是 // 一個(gè)方法對(duì)應(yīng)一個(gè) @cheduled 集合 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); }); if (annotatedMethods.isEmpty()) { // 如果當(dāng)前類 targetClass 不包含任何使用注解 @Scheduled 的方法,將其添加到 this.nonAnnotatedClasses this.nonAnnotatedClasses.add(targetClass); if (logger.isTraceEnabled()) { logger.trace("No @Scheduled annotations found on bean class: " + targetClass); } } else { // Non-empty set of methods // 當(dāng)前類 targetClass 上找到了使用注解 @Scheduled 的方法,記錄在 annotatedMethods 中, // 現(xiàn)在將它們逐個(gè)處理,使用的處理為方法 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; }
#rocessScheduled處理方法上的每個(gè)@Scheduled注解,生成一個(gè)ScheduledTask并登記到this.scheduledTasks。this.scheduledTasks數(shù)據(jù)結(jié)構(gòu)為 :
- Map數(shù)據(jù)類型;
- key是一個(gè)對(duì)象,其類就是含有方法使用了注解@Scheduled的類;
- value是一個(gè)ScheduledTask集合,方法上的每個(gè)注解@Scheduled對(duì)應(yīng)一個(gè)ScheduledTask;
實(shí)例分析
舉例來(lái)講,加入組件MyScheduledTask類中有兩個(gè)方法#method1,#method2上一用使用了五個(gè)注解Scheduled,則this.scheduledTasks會(huì)出現(xiàn)一項(xiàng)針對(duì)組件MyScheduledTask bean的項(xiàng),key是組件MyScheduledTask bean對(duì)象自身,value是五個(gè)ScheduledTask。
/** * Process the given {@code @Scheduled} method declaration on the given bean. * @param scheduled the @Scheduled annotation * @param method the method that the annotation has been declared on * @param bean the target bean instance * @see #createRunnable(Object, Method) */ protected void processScheduled(Scheduled scheduled, Method method, Object bean) { try { // 將使用了 @Scheduled 注解的方法包裝成一個(gè) Runnable 對(duì)象 , 隨后構(gòu)建 ScheduledTask 對(duì)象時(shí) // 會(huì)用得到 Runnable runnable = createRunnable(bean, method); // 用于記錄當(dāng)前 @Scheduled 注解是否已經(jīng)被處理,初始化為 false boolean processedSchedule = false; String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required"; // 用于保存針對(duì)當(dāng)前 @Scheduled 注解生成的 ScheduledTask, // 該方法完成時(shí),該集合內(nèi)元素?cái)?shù)量通常為 1 Set<ScheduledTask> tasks = new LinkedHashSet<>(4); // Determine initial delay // 確定 initial delay 屬性 : 基于注解屬性 initialDelay 或者 initialDelayString 分析得到, // 二者只能使用其中之一 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 // 檢查這是否是一個(gè) cron 表達(dá)式類型的注解 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(); } // 包裝成為一個(gè) CronTask 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 // 檢查這是否是一個(gè)固定延遲類型的注解 long fixedDelay = scheduled.fixedDelay(); if (fixedDelay >= 0) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; // 包裝成為一個(gè) FixedDelayTask 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"); } // 包裝成為一個(gè) FixedDelayTask tasks.add(this.registrar.scheduleFixedDelayTask( new FixedDelayTask(runnable, fixedDelay, initialDelay))); } } // Check fixed rate // 檢查這是否是一個(gè)固定周期執(zhí)行類型的注解 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"); } // 包裝成為一個(gè) FixedRateTask 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īng)過(guò)ScheduledAnnotationBeanPostProcessor以上這些處理,每個(gè)bean中所包含的@Scheduled注解都被發(fā)現(xiàn)了,這樣的每條信息最終對(duì)應(yīng)生成一個(gè)ScheduledTask,該ScheduledTask會(huì)被ScheduledTaskRegistrar registrar登記調(diào)度。這意味著該ScheduledTask從此刻起在程序運(yùn)行期間就會(huì)按照@Scheduled注解所設(shè)定的時(shí)間點(diǎn)被執(zhí)行。
到此這篇關(guān)于Spring中@EnableScheduling注解的工作原理詳解的文章就介紹到這了,更多相關(guān)@EnableScheduling注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java SpringBoot在RequestBody中高效的使用枚舉參數(shù)原理案例詳解
這篇文章主要介紹了Java SpringBoot在RequestBody中高效的使用枚舉參數(shù)原理案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09springboot整合Excel填充數(shù)據(jù)代碼示例
這篇文章主要給大家介紹了關(guān)于springboot整合Excel填充數(shù)據(jù)的相關(guān)資料,文中通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用springboot具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler
這篇文章主要為大家介紹了MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08SpringBoot API接口超時(shí)時(shí)間的五種配置方式詳解
在開(kāi)發(fā)API接口時(shí),配置API接口的超時(shí)時(shí)間是一項(xiàng)非常重要的任務(wù),SpringBoot中有多種方式可以配置API接口的超時(shí)時(shí)間,下面小編就為大家介紹一下吧2025-03-03Spring更簡(jiǎn)單的存儲(chǔ)方式與獲取方式詳解
Spring是一個(gè)輕量級(jí)的IoC和AOP容器框架,是為Java應(yīng)用程序提供基礎(chǔ)性服務(wù)的一套框架,目的是用于簡(jiǎn)化企業(yè)應(yīng)用程序的開(kāi)發(fā),它使得開(kāi)發(fā)者只需要關(guān)心業(yè)務(wù)需求,下面這篇文章主要給大家介紹了關(guān)于Spring更簡(jiǎn)單的存儲(chǔ)方式與獲取方式的相關(guān)資料,需要的朋友可以參考下2022-06-06Java實(shí)現(xiàn)常用緩存淘汰算法:FIFO、LRU、LFU
在高并發(fā)、高性能的質(zhì)量要求不斷提高時(shí),我們首先會(huì)想到的就是利用緩存予以應(yīng)對(duì)。而常用的幾個(gè)緩存淘汰算法有:FIFO、LRU和LFU,本文將為大家詳細(xì)介紹一下這三個(gè)算法并用java實(shí)現(xiàn),感興趣的可以跟隨小編一起學(xué)習(xí)一下2021-12-12MyBatis-Plus解決邏輯刪除與唯一索引的問(wèn)題
本文主要介紹了MyBatis-Plus解決邏輯刪除與唯一索引的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08JAVA8發(fā)送帶有Body的HTTP GET請(qǐng)求
本文主要介紹了JAVA8發(fā)送帶有Body的HTTP GET請(qǐng)求,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06Springboot整合分頁(yè)插件PageHelper步驟解析
這篇文章主要介紹了Springboot整合分頁(yè)插件PageHelper步驟解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Dwr3.0純注解(純Java Code配置)配置與應(yīng)用淺析二之前端調(diào)用后端
我們講到了后端純Java Code的Dwr3配置,完全去掉了dwr.xml配置文件,但是對(duì)于使用注解的類卻沒(méi)有使用包掃描,而是在Servlet初始化參數(shù)的classes里面加入了我們的Service組件的聲明暴露,對(duì)于這個(gè)問(wèn)題需要后面我們?cè)偌?xì)細(xì)研究下這篇文章,主要分析介紹前端怎么直接調(diào)用后端2016-04-04