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

Spring定時任務關于@EnableScheduling的用法解析

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

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

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

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

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

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

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

破案了,總結一下

配置類上標注@EnableScheduling注解,@EnableScheduling里面聚合了@Import,@Import最終會導入一個ScheduledAnnotationBeanPostProcessor。

ScheduledAnnotationBeanPostProcessor(ScheduleTask實現(xiàn)的重點)

1. ScheduledAnnotationBeanPostProcessor類圖

在這里插入圖片描述

下面對ScheduledAnnotationBeanPostProcessor實現(xiàn)的接口逐一說明

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

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

2. 針對上面接口幾個重點方法說明

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

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

下面會根據(jù)這種邏輯來解析Spring中的定時任務。

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

相關方法 postProcessAfterInitialization

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

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

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

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

封裝了bean和@Schedule標注的方法,將他倆封裝為一個Runnable。

2.CronTrigger

Trigger表示觸發(fā)器,在@Schedule注解里面每一個類型都對應不同的Trigger。

在這里插入圖片描述

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

3.CronTask

Task表示任務,task不是接口,是一個類。

在這里插入圖片描述

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

4.ScheduledTask

首先,他是final的

有兩個屬性值

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

總的來說,ScheduledTask保留了task提交給scheduledExecutorServic之后的引用對象和task任務。

5.ScheduledTaskRegistrar

在這里插入圖片描述

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

通過ScheduledTaskRegistrar可以注冊ScheduledTask,也能拿到ScheduledTask,所以,之后的重點就看看ScheduledTaskRegistrar的邏輯

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

3. 按照觸發(fā)的條件來調(diào)度定時任務。(onApplicationEvent)

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

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

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

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

上面的邏輯是提交的整個流程,下面,在來看看TaskScheduler的相關東西。最后的任務肯定都是通過他來執(zhí)行的。重中之重

4. TaskScheduler(重中之重)分析

在這里插入圖片描述

注意:這個接口默認實現(xiàn)是ThreadPoolTaskScheduler,下面就針對ThreadPoolTaskSchedulercronTask來做詳細的說明

TaskScheduler 任務的接口里面的詳細的方法,在這里就不展示了,因為太多了,這里就用ScheduledFuture<?> schedule(Runnable task, Trigger trigger);來做詳細的說明

在這里插入圖片描述

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

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

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

補充:

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

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

RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();

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

線程名字

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

默認的前綴是

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

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

  • LoggingErrorHandler(默認)
  • PropagatingErrorHandler(打日志,之后報錯)

到這里,還差最后一步,就是怎么按照cron表達式來運行定時任務,并且,到現(xiàn)在為止,沒看到CronTrigger的nextExecutionTime

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

在這里插入圖片描述

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

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

這兩個方法涉及的確實很精妙,避免了間隔的問題,達到時間絕對的標準。

定時任務還有有一種設計方法

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

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

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

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

但是,Spring的這種方式是覺不會出現(xiàn)這樣的問題,并且這種方式還支持取消任務。因為維護了Future引用。

重點

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

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

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

總結

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

相關文章

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

    Java利用反射實現(xiàn)框架類的方法實例

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

    Springboot hibernate envers使用過程詳解

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

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

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

    SpringBoot3集成MyBatis詳解

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

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

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

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

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

    實例講解Java設計模式編程中的OCP開閉原則

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

    RedisKey的失效監(jiān)聽器KeyExpirationEventMessageListener問題

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

    java線程之死鎖

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

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

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

最新評論