欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring中的@Scheduled源碼解析

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

@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)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring中的@CrossOrigin注冊(cè)處理方法源碼解析

    Spring中的@CrossOrigin注冊(cè)處理方法源碼解析

    這篇文章主要介紹了Spring中的@CrossOrigin注冊(cè)處理方法源碼解析,@CrossOrigin是基于@RequestMapping,@RequestMapping注釋方法掃描注冊(cè)的起點(diǎn)是equestMappingHandlerMapping.afterPropertiesSet(),需要的朋友可以參考下
    2023-12-12
  • 使用jvm sandbox對(duì)三層嵌套類型的改造示例

    使用jvm sandbox對(duì)三層嵌套類型的改造示例

    這篇文章主要為大家介紹了使用jvm sandbox對(duì)三層嵌套類型的改造示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • Java CAS原子操作詳解

    Java CAS原子操作詳解

    在synchronized的優(yōu)化過程中我們看到大量使用了CAS操作,CAS全稱Compare And Set(或Compare And Swap),簡單來說CAS操作就是一個(gè)虛擬機(jī)實(shí)現(xiàn)的原子操作
    2023-02-02
  • 基于Java實(shí)現(xiàn)簡易的七星彩號(hào)碼生成器

    基于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ù)

    本篇文章主要介紹了詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-03-03
  • 快速入門Java中的Lambda表達(dá)式

    快速入門Java中的Lambda表達(dá)式

    Lambda作為函數(shù)式編程中的基礎(chǔ)部分,在其他編程語言中早就廣為使用,但在Java領(lǐng)域中發(fā)展較慢,直到j(luò)ava8,才開始支持Lambda。網(wǎng)上關(guān)于Lambda的教程很多,今天小編給大家分享一篇快速入手Lambda的教程。
    2016-08-08
  • Spring-ImportSelector接口功能使用案例

    Spring-ImportSelector接口功能使用案例

    ImportSelector接口是至spring中導(dǎo)入內(nèi)部類或者外部類的核心接口,只需要其定義的方法內(nèi)返回需要?jiǎng)?chuàng)建bean的class字符串就好了,這篇文章主要介紹了Spring-ImportSelector接口功能介紹,需要的朋友可以參考下
    2023-09-09
  • @CacheEvict注解,清除緩存方式

    @CacheEvict注解,清除緩存方式

    這篇文章主要介紹了@CacheEvict注解,清除緩存方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Java 垃圾回收機(jī)制詳解及實(shí)例代碼

    Java 垃圾回收機(jī)制詳解及實(shí)例代碼

    這篇文章主要介紹了 Java 垃圾回收機(jī)制詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • MyBatis-Plus實(shí)現(xiàn)多表聯(lián)查的方法實(shí)戰(zhàn)

    MyBatis-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

最新評(píng)論