最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring中的@Scheduled源碼解析

 更新時間:2023年09月25日 09:15:17   作者:木棉軟糖  
這篇文章主要介紹了Spring中的@Scheduled源碼解析,定時任務調度的基礎是ScheduledAnnotationBeanPostProcessor類,這是一個實現了BeanPostProcessor接口的后置處理器,需要的朋友可以參考下

@Scheduled源碼解析

解析部分

定時任務調度的基礎是ScheduledAnnotationBeanPostProcessor類,這是一個實現了BeanPostProcessor接口的后置處理器。

關于BeanPostProcessor,最主要就是看postProcessBeforeInitialization方法和postProcessAfterInitialization方法做了什么邏輯。 postProcessBeforeInitialization方法沒有實現邏輯,所以看postProcessAfterInitialization方法的邏輯。

@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		/**
		* 上面省略部分代碼,看下面的關鍵代碼
		*/
		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注解的方法,當前bean加入到nonAnnotatedClasses集合中,不進行處理
			if (annotatedMethods.isEmpty()) {
				this.nonAnnotatedClasses.add(targetClass);
				if (logger.isTraceEnabled()) {
					logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
				}
			}
			else {
				//如果存在被@Scheduled和@Schedules注解的方法,針對每個方法調用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;
	}

根據以上代碼,總結出ScheduledAnnotationBeanPostProcessor 類做的事情:

(1)獲取被@Scheduled和@Schedules注解標記的方法,若沒有,將此Bean加入到nonAnnotatedClasses集合中。

(2)存在被@Scheduled和@Schedules注解的方法,針對每個方法調用processScheduled方法

所以,接下來就是分析關鍵在于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 的值,字符串和整型值不能同時配置
			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());
		}
	}

根據以上代碼,大致邏輯如下:

(1)解析initialDelay的值

(2)根據@Scheduled注解的屬性配置,分別將此bean的被注解//方法封裝為CronTask,FixedDelayTask,FixedRateTask

(3)this.registrar 根據封裝的任務類型使用對應的調度方法scheduleXXX 若此時taskScheduler局部變量還沒有初始化完成,那么將會加入到一個臨時的集合存起來,不進行調度,這個taskScheduler可以看做是一個調度任務專用的線程池

(4)調度方法返回的結果加入到tasks集合中

(5)然后按照bean分類,放入scheduledTasks集合(以bean為key的Map集合)

其中@Scheduled注解 的限制如下:

(1)cron表達式不能與initialDelay,fixedDelay,fixedRate一起配置

(2)fixedDelay不能與cron同時設置

(3)fixedRate不能與cron 同時配置

(4)fixedDelay 和fixedRate不能同時配置

至此postProcessAfterInitialization方法執(zhí)行完成。 粗略總結一下,這個方法就是把@Scheduled注解的方法解析出來,然后轉化為ScheduledTask,這大概是代表了一個定時任務的對象,然后再按bean分組存放到一個Map集合中。

執(zhí)行部分

經過驗證,其實上面在執(zhí)行postProcessAfterInitialization方法,taskScheduler還是為null的,也就是說,各個定時任務實際上還是沒辦法開始調度執(zhí)行。 舉個例子:

@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);
	}

此時由于taskScheduler 為null,因此沒有執(zhí)行this.taskScheduler.scheduleAtFixedRate方法,而是調用了addFixedRateTask(task)。(經過測試,就算自定義了taskScheduler,也不會在這時候賦值的) 那上面的this.taskScheduler.scheduleAtFixedRate方法 在什么執(zhí)行?帶著這個疑問,調試打點,最終發(fā)現在onApplicationEvent方法中它才會執(zhí)行調度,此時taskScheduler不為空。

ScheduledAnnotationBeanPostProcessor 類實現了ApplicationListener接口,監(jiān)聽ContextRefreshedEvent 事件。根據以前學習的Spirng加載流程,ContextRefreshedEvent 事件是Spring容器加載完成之后,執(zhí)行finishRefesh方法時發(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();
	}

有一個地方,個人覺得值得了解的: 片段1:自定義調度線程池時實現了SchedulingConfigurer接口 的configureTasks方法,這個方法就是在片段1執(zhí)行的。

然后之后比較重要的。主要看this.registrar.afterPropertiesSet方法 this.registrar.afterPropertiesSet方法里面調用了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()。

如果是自定義線程池,則不會執(zhí)行,因為此時已經賦值了。

片段2:根據不同的定時任務類型,分別調用不同的調度API

這里的this.cronTasks,this.fixedRateTasks,this.fixedDelayTasks 就是上面執(zhí)行processScheduled方法時,因為this.taskScheduler 為null而把定時任務臨時存放的地方。 因為現在已經有this.taskScheduler ,因此正式將它們加入調度,并放入scheduledTasks 集合中(已經參與調度的不會重復加入)。

小結

(1)從這個源碼分析,可以知道通過實現SchedulingConfigurer接口自定義調度線程池的配置

(2)@Scheduled注解 的限制,不能同時配置多種任務類型

到此這篇關于Spring中的@Scheduled源碼解析的文章就介紹到這了,更多相關@Scheduled源碼解析內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 一文帶你吃透Java中的String類

    一文帶你吃透Java中的String類

    在 Java 中,字符串是一種常見的數據類型,經常用于存儲一些文本信息,而String類則是Java提供的專門用于字符串操作的類,本文就來和大家聊聊String類的常用方法與實現原理吧
    2023-05-05
  • Java校驗是否為連續(xù)的區(qū)間問題

    Java校驗是否為連續(xù)的區(qū)間問題

    這篇文章主要介紹了Java校驗是否為連續(xù)的區(qū)間問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Spring 框架實現賬戶轉賬功能(推薦)

    Spring 框架實現賬戶轉賬功能(推薦)

    通過本文的介紹,我們了解了如何使用Spring框架實現一個簡單的賬戶轉賬功能,主要使用了 Spring 的依賴注入、和事務管理功能,保證了轉賬操作的原子性和數據的一致性,感興趣的朋友跟隨小編一起看看吧
    2025-07-07
  • SpringBoot?@Scheduled?Cron表達式使用方式

    SpringBoot?@Scheduled?Cron表達式使用方式

    這篇文章主要介紹了SpringBoot?@Scheduled?Cron表達式使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • 如何解決springboot上傳文件路徑找不到的問題

    如何解決springboot上傳文件路徑找不到的問題

    這篇文章主要介紹了如何解決springboot上傳文件路徑找不到的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • 使用Prometheus+Grafana的方法監(jiān)控Springboot應用教程詳解

    使用Prometheus+Grafana的方法監(jiān)控Springboot應用教程詳解

    這篇文章主要介紹了用Prometheus+Grafana的方法監(jiān)控Springboot應用,本文通過實例代碼詳解給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • Java Executor 框架的實例詳解

    Java Executor 框架的實例詳解

    這篇文章主要介紹了Java Executor 框架的實例詳解的相關資料,這里提供實例來幫助大家學習理解這部分內容,需要的朋友可以參考下
    2017-09-09
  • SpringBoot利用ThreadPoolTaskExecutor批量插入百萬級數據

    SpringBoot利用ThreadPoolTaskExecutor批量插入百萬級數據

    在處理大量數據時,為了提高效率和性能,通常需要采用批量插入的方式,本文主要介紹了SpringBoot利用ThreadPoolTaskExecutor批量插入百萬級數據,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • Java微服務實戰(zhàn)項目尚融寶接口創(chuàng)建詳解

    Java微服務實戰(zhàn)項目尚融寶接口創(chuàng)建詳解

    這篇文章主要介紹了Java微服務實戰(zhàn)項目尚融寶的接口創(chuàng)建流程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • java中int、double、char等變量的取值范圍詳析

    java中int、double、char等變量的取值范圍詳析

    這篇文章主要給大家介紹了關于java中int、double、char等變量取值范圍的相關資料,每個變量都給出了詳細的實例代碼,對大家學習或者使用java具有一定的參考學習價值,需要的朋友可以參考下
    2021-10-10

最新評論

上思县| 金塔县| 那曲县| 永兴县| 武清区| 米脂县| 阜新市| 彭泽县| 永善县| 吉水县| 聂荣县| 辛集市| 永清县| 维西| 乡宁县| 虞城县| 乌拉特中旗| 屏东县| 万安县| 神农架林区| 连云港市| 泽州县| 瓦房店市| 吐鲁番市| 白水县| 长沙市| 泰兴市| 津南区| 东源县| 禄丰县| 崇文区| 凤翔县| 随州市| 梨树县| 双鸭山市| 德清县| 开原市| 宝坻区| 沛县| 杭锦旗| 镇康县|