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

Spring定時(shí)任務(wù)關(guān)于@EnableScheduling的用法解析

 更新時(shí)間:2023年06月27日 09:45:18   作者:daliucheng  
這篇文章主要介紹了Spring定時(shí)任務(wù)關(guān)于@EnableScheduling的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

一切的開(kāi)始(@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類(lèi)
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注解里面有什么,再找個(gè)類(lèi)上面spring已經(jīng)很明確得告知,這個(gè)注解得作用和相關(guān)得拓展方式了,有興趣可以下載看看。這里就不寫(xiě)了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)//
@Documented
public @interface EnableScheduling {
}

注意@Import注解導(dǎo)入得SchedulingConfiguration。@Import注解是@Configuration一塊使用。這里就不分析在Spring里面怎么解析配置類(lèi)得了,springboot自動(dòng)裝配原理注解@EnableAutoConfiguration啟動(dòng)得關(guān)鍵就在于這里。這部分得內(nèi)容之后再寫(xiě)。

繼續(xù)看,看看SchedulingConfiguration是什么,里面干了什么事情。

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) //spring里面自己用得bean,和用戶(hù)自定沒(méi)有關(guān)系
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 用戶(hù)自定義得bean
  • ROLE_SUPPORT 輔助角色
  • ROLE_INFRASTRUCTURE 表示完全和用戶(hù)得bean沒(méi)有關(guān)系,表示一個(gè)在容器里面自己使用得。

到這里就很肯定了,ScheduledAnnotationBeanPostProcessor就是ScheduleTask實(shí)現(xiàn)的重點(diǎn)。

破案了,總結(jié)一下

配置類(lèi)上標(biāo)注@EnableScheduling注解,@EnableScheduling里面聚合了@Import,@Import最終會(huì)導(dǎo)入一個(gè)ScheduledAnnotationBeanPostProcessor。

ScheduledAnnotationBeanPostProcessor(ScheduleTask實(shí)現(xiàn)的重點(diǎn))

1. ScheduledAnnotationBeanPostProcessor類(lèi)圖

在這里插入圖片描述

下面對(duì)ScheduledAnnotationBeanPostProcessor實(shí)現(xiàn)的接口逐一說(shuō)明

  • MergedBeanDefinitionPostProcessor是 BeanPostProcessor,在BeanPostProcessor的基礎(chǔ)上增加了postProcessMergedBeanDefinition,這個(gè)接口的主要的實(shí)現(xiàn)類(lèi)如下,其中最重要的就是AutowiredAnnotationBeanPostProcessor用于處理Autowired。
//在實(shí)例化出來(lái)之后,在調(diào)用postProcessAfterInitialization之前會(huì)調(diào)用postProcessMergedBeanDefinition。
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
//初始化之前
@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
//重點(diǎn)是這個(gè)方法。等會(huì)看看在ScheduledAnnotationBeanPostProcessor里面干了什么事情。
@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
  • DestructionAwareBeanPostProcessor繼承于BeanPostProcessor,在之前的基礎(chǔ)上面,增加了兩個(gè)方法,用于判斷是否需要銷(xiāo)毀,和用于銷(xiāo)毀bean的之前調(diào)用。
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
default boolean requiresDestruction(Object bean) {
		return true;
	}
  • 實(shí)現(xiàn)了很多的rAware接口,Aware接口沒(méi)有什么要說(shuō)的。
  • SmartInitializingSingleton在spring中bean創(chuàng)建完成之后的回調(diào).
//在所有的bean實(shí)例化完成之后,如果bean實(shí)現(xiàn)了SmartInitializingSingleton接口,就會(huì)調(diào)用afterSingletonsInstantiated方法。
void afterSingletonsInstantiated()
  • ApplicationListener<ContextRefreshedEvent>時(shí)間監(jiān)聽(tīng),范型里面的ContextRefreshedEvent表示關(guān)心的具體事件。
  • 在整個(gè)spring容器創(chuàng)建好,對(duì)象也創(chuàng)建好,SmartInitializingSingleton調(diào)用之后,一直在最后的最后,會(huì)發(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.  重點(diǎn)就是這個(gè)。發(fā)布ContextRefreshedEvent事件,表示活都干完了。
		publishEvent(new ContextRefreshedEvent(this));
		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}

DisposableBean在bean銷(xiāo)毀的時(shí)候調(diào)用,bean銷(xiāo)毀的時(shí)候的生命周期是,先調(diào)用DestructionAwareBeanPostProcessor#postProcessBeforeDestruction,接著是DisposableBean#destroy方法,后面才是用戶(hù)自定義的destroy方法。

2. 針對(duì)上面接口幾個(gè)重點(diǎn)方法說(shuō)明

題外話,定時(shí)任務(wù)大體的實(shí)現(xiàn)是什么?

  • 想盡方法拿到被@Schedule修飾的方法。
  • 將這些方法上的@Schedule標(biāo)注的解析,保存映射關(guān)系。
  • 按照觸發(fā)的條件來(lái)調(diào)度定時(shí)任務(wù)。

下面會(huì)根據(jù)這種邏輯來(lái)解析Spring中的定時(shí)任務(wù)。

1. 想盡方法拿到被@Schedule修飾的方法。

相關(guān)方法 postProcessAfterInitialization

首先要知道,postProcessAfterInitialization在SpringBean的生命周期中在那一個(gè)環(huán)節(jié),要解視這個(gè)問(wèn)題,要先知道SpringBean的生命周期是什么?那么這個(gè)就繁瑣了,生命周期的文檔多的是,找一個(gè)看看就可以。簡(jiǎn)單的說(shuō),就是Spring在初始化完成的最后一步會(huì)調(diào)用postProcessAfterInitialization。當(dāng)然,代理對(duì)象的創(chuàng)建也是在這里。

那么,下面就對(duì)源碼分析分析,源碼不難,看的懂,并且我添加了注釋

@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (bean instanceof AopInfrastructureBean) {
			// Ignore AOP infrastructure such as scoped proxies.
			return bean;
		}
		//得到?jīng)]有被裝飾的最原始的類(lèi),就是cglib增強(qiáng)之前的原始的類(lèi),并且這里也能說(shuō)明CGLib是通過(guò)繼承來(lái)實(shí)現(xiàn)增強(qiáng)的
		Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
		if (!this.nonAnnotatedClasses.contains(targetClass)) {
			Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,//找處了所有的被Scheduled標(biāo)注的方法
					(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標(biāo)注的解析,保存映射關(guān)系(processScheduled方法解析)

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
		try {
			//斷言,判單方法是否有參數(shù),如果有參數(shù)就報(bào)錯(cuò)
			Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");//這里會(huì)判斷 getParameterCount==0,如果不是0的話,就報(bào)錯(cuò)。這也是spring里面定時(shí)任務(wù)比較雞肋的方法,但是這個(gè)我覺(jué)得并沒(méi)有啥子問(wèn)題,誰(shuí)在定時(shí)任務(wù)執(zhí)行的時(shí)候需要傳遞參數(shù)
			//判斷這個(gè)方法是否是靜態(tài),私有的,
			Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
			//將需要執(zhí)行的方法封裝成ScheduledMethodRunnable,這個(gè)類(lèi)實(shí)現(xiàn)很簡(jiǎn)單,就倆屬性,
			//target表示bean
			//method表示需要執(zhí)行的方法
			Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
			//標(biāo)志位,開(kāi)始都是false,只要找到@Scheduled注解,并且解析到參數(shù),就是false,后面還會(huì)對(duì)他進(jìn)行判斷。
			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就是后面的這個(gè)樣子    "PT20.345S" -- parses as "20.345 seconds" "PT15M"     -- parses as "15 minutes" (where a minute is 60
				if (this.embeddedValueResolver != null) {
					//這個(gè)意味著,這里的initialDelayString是可以寫(xiě)SPEL表達(dá)式的。embeddedValueResolver處理器很常見(jiàn),會(huì)從環(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表達(dá)式
			String cron = scheduled.cron();
			if (StringUtils.hasText(cron)) {
				String zone = scheduled.zone();
				if (this.embeddedValueResolver != null) {//這里也能處理
					//事實(shí)就是這樣,利用embeddedValueResolver來(lái)處理值,很巧妙。
					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之后,標(biāo)志位變?yōu)閠rue
					processedSchedule = true;
					TimeZone timeZone;
					if (StringUtils.hasText(zone)) {
						timeZone = StringUtils.parseTimeZoneString(zone);
					}
					else {
						timeZone = TimeZone.getDefault();
					}
					//將cron表達(dá)式變?yōu)镃ronTrigger,
					//將runnable(ScheduledMethodRunnable)變?yōu)镃ronTask
					//將CronTask變?yōu)閟cheduleCronTask,并且將CronTask添加到 registrar的cronTasks屬性去,并還維護(hù)了CronTask和scheduleCronTask的映射關(guān)系。
					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的類(lèi)型不同。其余都是一樣的。
			// Check whether we had any attribute set 檢查一下,檢查是@schedule里面必須的參數(shù)是否都有
			Assert.isTrue(processedSchedule, errorMessage);
			//在解析完成之后,將bean和@schedule保存在map里面,map的key是bean,value是set,set存放的是這個(gè)bean里面被@schedule標(biāo)注方法的集合,
			// 也就是ScheduledTask集合
			//但是這里的加鎖操作,我沒(méi)有看懂,不知道這個(gè)是干嘛的?
			//這里會(huì)有并發(fā)的問(wèn)題嗎?首先他是在postProcessAfterInitialization方法里面起作用的,這個(gè)方法在spring解析bean的時(shí)候起作用的
			//并且spring調(diào)用beanPostProcess都是順序調(diào)用。不存在并發(fā)問(wèn)題。
			// 所以這里的鎖,我沒(méi)有看懂。
			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());
		}
	}

問(wèn)題:

上面代碼中加鎖操作我沒(méi)有看懂,有大佬能告知我一下。

說(shuō)明:

對(duì)于上面代碼中幾個(gè)重點(diǎn)的類(lèi)說(shuō)明

1.ScheduledMethodRunnable

封裝了bean和@Schedule標(biāo)注的方法,將他倆封裝為一個(gè)Runnable。

2.CronTrigger

Trigger表示觸發(fā)器,在@Schedule注解里面每一個(gè)類(lèi)型都對(duì)應(yīng)不同的Trigger。

在這里插入圖片描述

Trigger里面最核心的方法是`Date nextExecutionTime(TriggerContext triggerContext);` 下一次執(zhí)行的時(shí)間,那么對(duì)于Cron或者PeriodTigger都是計(jì)算下一次執(zhí)行的時(shí)間。

3.CronTask

Task表示任務(wù),task不是接口,是一個(gè)類(lèi)。

在這里插入圖片描述

task中最核心的方法是getRunnable,TiggerTask在它的基礎(chǔ)上增加了getTrigger,CronTask在之前的基礎(chǔ)上增加了getExpression()

4.ScheduledTask

首先,他是final的

有兩個(gè)屬性值

private final Task task;//任務(wù)
    //保留的是ScheduledFuture的引用,ScheduledFuture是scheduledExecutorService提交任務(wù)之后的引用對(duì)象。
	//ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
	//	}, 0, 0, TimeUnit.SECONDS);
	@Nullable
	volatile ScheduledFuture<?> future;

總的來(lái)說(shuō),ScheduledTask保留了task提交給scheduledExecutorServic之后的引用對(duì)象和task任務(wù)。

5.ScheduledTaskRegistrar

在這里插入圖片描述

  • InitializingBean 在調(diào)用自定義init方法之前調(diào)用
  • DisposableBean 上面說(shuō)了,在調(diào)用自定義destroy方法之前調(diào)用
  • ScheduledTaskHolder,通過(guò)這個(gè)接口能拿到所有的ScheduledTask

通過(guò)ScheduledTaskRegistrar可以注冊(cè)ScheduledTask,也能拿到ScheduledTask,所以,之后的重點(diǎn)就看看ScheduledTaskRegistrar的邏輯

到這里,已經(jīng)完成了從bean中檢索Scheduled修飾的方法,并且解析ScheduledTask注解的屬性,轉(zhuǎn)換為對(duì)應(yīng)的Bean,通過(guò)ScheduledTaskRegistrar注冊(cè)到ScheduledTaskRegistrar里面。

3. 按照觸發(fā)的條件來(lái)調(diào)度定時(shí)任務(wù)。(onApplicationEvent)

前面說(shuō)過(guò),ScheduledAnnotationBeanPostProcessor實(shí)現(xiàn)了ApplicationListener<ContextRefreshedEvent>接口,Spring會(huì)在所有的活都干完之后,發(fā)布一個(gè)ContextRefreshedEvent事件。重點(diǎn)就在于它。下面看看它里面干了什么事情

@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重點(diǎn)是它。繼續(xù)沖

protected void scheduleTasks() {
		//如果說(shuō)在spring中沒(méi)有 TaskScheduler的實(shí)現(xiàn)類(lèi),也沒(méi)有ScheduledExecutorService的實(shí)現(xiàn)類(lèi),那就自己默認(rèn)來(lái)一個(gè),
		//  Executors.newSingleThreadScheduledExecutor(); 核心線程是1,但是最大線程數(shù)是Max。
		// 通過(guò)ConcurrentTaskScheduler包裝。
		if (this.taskScheduler == null) {
			this.localExecutor = Executors.newSingleThreadScheduledExecutor();
			this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
		}
		//下面就是挨個(gè)從之前注冊(cè)的這些task里面從 ScheduledTaskRegistrar里面維護(hù)的雙向映射關(guān)系中獲取scheduledTask,通過(guò)
		//  ScheduledTaskRegistrar中的taskScheduled提交任務(wù),將返回的Future對(duì)象保存在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的種類(lèi)可能不一樣,并且提交個(gè)taskSchedule的方法可能不一樣

//首先這個(gè)方法是ScheduledTaskRegistrar的	
@Nullable
	public ScheduledTask scheduleCronTask(CronTask task) {
        //之前保存在解析schedule的時(shí)候保存的CronTask和ScheduledTask之前的引用關(guān)系。
		ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
		boolean newTask = false;
        //這里肯定是有的,所以,肯定不會(huì)從這里走,所以newTask肯定是fasle,那這個(gè)方法的返回值肯定是null
		if (scheduledTask == null) {
			scheduledTask = new ScheduledTask(task);
			newTask = true;
		}
        //提交任務(wù)
		if (this.taskScheduler != null) {
            //提交任務(wù),接下來(lái)就看看taskScheduler相關(guān)的就好了
			scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
		}
		else {
			addCronTask(task);
			this.unresolvedTasks.put(task, scheduledTask);
		}
		return (newTask ? scheduledTask : null);
	}

上面的邏輯是提交的整個(gè)流程,下面,在來(lái)看看TaskScheduler的相關(guān)東西。最后的任務(wù)肯定都是通過(guò)他來(lái)執(zhí)行的。重中之重

4. TaskScheduler(重中之重)分析

在這里插入圖片描述

注意:這個(gè)接口默認(rèn)實(shí)現(xiàn)是ThreadPoolTaskScheduler,下面就針對(duì)ThreadPoolTaskSchedulercronTask來(lái)做詳細(xì)的說(shuō)明

TaskScheduler 任務(wù)的接口里面的詳細(xì)的方法,在這里就不展示了,因?yàn)樘嗔?,這里就用ScheduledFuture<?> schedule(Runnable task, Trigger trigger);來(lái)做詳細(xì)的說(shuō)明

在這里插入圖片描述

**提示:**默認(rèn)在Spring里面是不會(huì)自動(dòng)創(chuàng)建的,需要手動(dòng)的聲明@Bean,這里要注意,他InitializingBean和DisposableBean。這里面肯定有初始化操作和銷(xiāo)毀操作

繼續(xù)看,接著上面看 schedule方法詳情

@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
   //得到執(zhí)行器
   //關(guān)于這個(gè)執(zhí)行器的參數(shù)的設(shè)置可以看ExecutorConfigurationSupport,
   ScheduledExecutorService executor = getScheduledExecutor();
   try {
      //錯(cuò)誤處理的handle。有兩種handle,一種是出錯(cuò)了之后打印日志,一種是拋異常
      // LoggingErrorHandler
      // PropagatingErrorHandler
      ErrorHandler errorHandler = this.errorHandler;
      if (errorHandler == null) {
         errorHandler = TaskUtils.getDefaultErrorHandler(true);
      }
      //將執(zhí)行器,task,trigger包裝成ReschedulingRunnable,schedule方法通過(guò)將這個(gè)任務(wù)提交給executor,但是這里寫(xiě)的很巧妙
      return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
   }
   catch (RejectedExecutionException ex) {
      throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
   }
}

補(bǔ)充:

1.創(chuàng)建執(zhí)行器線程的相關(guān)邏輯要看ExecutorConfigurationSupport

這里列舉線程池配置的參數(shù)

RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();

線程的優(yōu)先級(jí)是 5

線程名字

return getThreadNamePrefix() + this.threadCount.incrementAndGet();

默認(rèn)的前綴是

return ClassUtils.getShortName(getClass()) + "-";
corePoolSize=1
maximumPoolSize = MAX_VALUE
ttl=0
queue=new DelayedWorkQueue(), //這是xecutor默認(rèn)的隊(duì)列

2.Executor執(zhí)行失敗之后,異常處理默認(rèn)策略有兩種(主要實(shí)現(xiàn)ErrorHandler接口,并且通過(guò)set方法也可以設(shè)置)

  • LoggingErrorHandler(默認(rèn))
  • PropagatingErrorHandler(打日志,之后報(bào)錯(cuò))

到這里,還差最后一步,就是怎么按照cron表達(dá)式來(lái)運(yùn)行定時(shí)任務(wù),并且,到現(xiàn)在為止,沒(méi)看到CronTrigger的nextExecutionTime

5. 怎么做調(diào)度(ReschedulingRunnable的具體實(shí)現(xiàn))

在這里插入圖片描述

首先他是一個(gè)ScheduledFuture。在這里主要看兩個(gè)方法runschedule,這里的實(shí)現(xiàn)很巧妙。

  • schedule方法?
@Nullable
	public ScheduledFuture<?> schedule() {
		synchronized (this.triggerContextMonitor) {
			//獲取下次任務(wù)執(zhí)行的事件
			this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
			if (this.scheduledExecutionTime == null) {
				return null;
			}
			//算出需要延遲啟動(dòng)的事件
			long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
			//延遲啟動(dòng),提交給executor的schedule
			this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
			return this;
		}
	}
  • run方法
@Override
	public void run() {
		Date actualExecutionTime = new Date();
		//開(kāi)始跑任務(wù),這里是通過(guò)反射調(diào)用的,
		//要知道這里的runnable是ScheduledMethodRunnable對(duì)象,反射調(diào)用就在ScheduledMethodRunnable里面的run方法
		//并且這里也有執(zhí)行失敗之后的異常處理
		super.run();
		Date completionTime = new Date();
		synchronized (this.triggerContextMonitor) {
			//scheduledExecutionTime這個(gè)不是null,因?yàn)樵谡{(diào)用的時(shí)候是先調(diào)用schedule方法的,在這個(gè)方法里面設(shè)置了scheduledExecutionTime(下次執(zhí)行的時(shí)間)
			Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");
			//更新triggerContext,triggerContext是在這個(gè)類(lèi)里面直接new出來(lái)的
			//上次執(zhí)行時(shí)間,上次執(zhí)行的耗費(fèi)的真正時(shí)間,完成任務(wù)時(shí)間
			this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
			if (!obtainCurrentFuture().isCancelled()) {
				//繼續(xù)調(diào)用schedule,這就是精髓,今天的重點(diǎn)。繼續(xù)注冊(cè),繼續(xù)循環(huán),
				schedule();
			}
		}
	}

這兩個(gè)方法涉及的確實(shí)很精妙,避免了間隔的問(wèn)題,達(dá)到時(shí)間絕對(duì)的標(biāo)準(zhǔn)。

定時(shí)任務(wù)還有有一種設(shè)計(jì)方法

1.將注解里面的cron解析出來(lái),然后維護(hù)在一個(gè)地方(內(nèi)存或者redis)

2.有兩個(gè)組件,調(diào)度器和執(zhí)行器,

  • 調(diào)度器
  • 每間隔多少秒去遍歷所有的任務(wù),看看下次執(zhí)行的時(shí)間是否比當(dāng)前時(shí)間小,如果小,那就說(shuō)明改執(zhí)行了,就將方法傳遞給執(zhí)行器,執(zhí)行器來(lái)執(zhí)行任務(wù),調(diào)度器還和之前一樣,間隔遍歷所有任務(wù)
  • 執(zhí)行器
  • 執(zhí)行任務(wù)

但是這樣存在一個(gè)問(wèn)題,比如間隔10秒,但是任務(wù)是需要4秒執(zhí)行一次,這就出現(xiàn)問(wèn)題了

但是,Spring的這種方式是覺(jué)不會(huì)出現(xiàn)這樣的問(wèn)題,并且這種方式還支持取消任務(wù)。因?yàn)榫S護(hù)了Future引用。

重點(diǎn)

ReschedulingRunnable中的schedule和run方法之前的關(guān)系,如果有一個(gè)任務(wù)每間隔5秒要執(zhí)行一次。

  • 開(kāi)始的時(shí)候是調(diào)用schedule方法,這個(gè)方法里面計(jì)算出下次執(zhí)行的時(shí)間,并且算出當(dāng)前時(shí)間和下次執(zhí)行時(shí)間的差值,然后通過(guò)延遲啟動(dòng),就能獲取精確的啟動(dòng)時(shí)間。
  • 提交之后就會(huì)運(yùn)行run方法,run方法會(huì)調(diào)用ScheduledMethodRunnable的run方法,ScheduledMethodRunnable里面會(huì)通過(guò)反射調(diào)用方法。
  • 運(yùn)行完成之后,會(huì)更新Context,保留這次執(zhí)行的相關(guān)信息,然后判斷future是否取消,沒(méi)有取消就繼續(xù)調(diào)用schedule方法,本次已經(jīng)執(zhí)行完成,下次的任務(wù)就繼續(xù)開(kāi)始了。

到這里,關(guān)于Spring中定時(shí)任務(wù)實(shí)現(xiàn)的已經(jīng)結(jié)束了。不禁感嘆,這種實(shí)現(xiàn)方式真的很精妙。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java利用反射實(shí)現(xiàn)框架類(lèi)的方法實(shí)例

    Java利用反射實(shí)現(xiàn)框架類(lèi)的方法實(shí)例

    這篇文章主要給大家介紹了關(guān)于Java利用反射實(shí)現(xiàn)框架類(lèi)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Springboot hibernate envers使用過(guò)程詳解

    Springboot hibernate envers使用過(guò)程詳解

    這篇文章主要介紹了Springboot hibernate envers使用過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Spring Mybatis 基本使用過(guò)程(推薦)

    Spring Mybatis 基本使用過(guò)程(推薦)

    Mybatis是一個(gè)半自動(dòng)ORM(Object Relational Mapping)框架,它可以簡(jiǎn)化數(shù)據(jù)庫(kù)編程,讓開(kāi)發(fā)者更專(zhuān)注于SQL本身,本文給大家介紹Spring Mybatis 基本使用過(guò)程,感興趣的朋友跟隨小編一起看看吧
    2024-09-09
  • SpringBoot3集成MyBatis詳解

    SpringBoot3集成MyBatis詳解

    MyBatis是一款開(kāi)源的持久層框架,它極大地簡(jiǎn)化了與數(shù)據(jù)庫(kù)的交互流程,MyBatis更具靈活性,允許開(kāi)發(fā)者直接使用SQL語(yǔ)句與數(shù)據(jù)庫(kù)進(jìn)行交互,本文將詳細(xì)介紹在Spring Boot項(xiàng)目中如何集成MyBatis,以實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的輕松訪問(wèn)和操作,需要的朋友可以參考下
    2023-12-12
  • SpringBoot超詳細(xì)講解多數(shù)據(jù)源集成

    SpringBoot超詳細(xì)講解多數(shù)據(jù)源集成

    今天分享下SpringBoot多數(shù)據(jù)源集成,我怕麻煩,這里我覺(jué)得我的集成也應(yīng)該是最簡(jiǎn)單的,清晰明了
    2022-05-05
  • Map如何根據(jù)key指定條件進(jìn)行過(guò)濾篩選

    Map如何根據(jù)key指定條件進(jìn)行過(guò)濾篩選

    這篇文章主要介紹了Map如何根據(jù)key指定條件進(jìn)行過(guò)濾篩選問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • 實(shí)例講解Java設(shè)計(jì)模式編程中的OCP開(kāi)閉原則

    實(shí)例講解Java設(shè)計(jì)模式編程中的OCP開(kāi)閉原則

    這篇文章主要介紹了Java設(shè)計(jì)模式編程中的開(kāi)閉原則,開(kāi)閉原則的大意被作者總結(jié)為用抽象構(gòu)建框架,用實(shí)現(xiàn)擴(kuò)展細(xì)節(jié),需要的朋友可以參考下
    2016-02-02
  • RedisKey的失效監(jiān)聽(tīng)器KeyExpirationEventMessageListener問(wèn)題

    RedisKey的失效監(jiān)聽(tīng)器KeyExpirationEventMessageListener問(wèn)題

    這篇文章主要介紹了RedisKey的失效監(jiān)聽(tīng)器KeyExpirationEventMessageListener問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • java線程之死鎖

    java線程之死鎖

    這篇文章主要介紹了Java線程之死鎖,死鎖是這樣一種情形-多個(gè)線程同時(shí)被阻塞,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放。由于線程被無(wú)限期地阻塞,因此程序不可能正常終止
    2022-05-05
  • IDEA中創(chuàng)建properties配置文件

    IDEA中創(chuàng)建properties配置文件

    我們?cè)趈2ee當(dāng)中,連接數(shù)據(jù)庫(kù)的時(shí)候經(jīng)常會(huì)用到properties配置文件,本文主要介紹了IDEA中創(chuàng)建properties配置文件,具有一定的參考價(jià)值,?感興趣的可以了解一下
    2024-04-04

最新評(píng)論