Spring定時(shí)任務(wù)關(guān)于@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注解里面有什么,再找個(gè)類上面spring已經(jīng)很明確得告知,這個(gè)注解得作用和相關(guān)得拓展方式了,有興趣可以下載看看。這里就不寫了。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)//
@Documented
public @interface EnableScheduling {
}注意@Import注解導(dǎo)入得SchedulingConfiguration。@Import注解是@Configuration一塊使用。這里就不分析在Spring里面怎么解析配置類得了,springboot自動(dòng)裝配原理注解@EnableAutoConfiguration啟動(dòng)得關(guān)鍵就在于這里。這部分得內(nèi)容之后再寫。
繼續(xù)看,看看SchedulingConfiguration是什么,里面干了什么事情。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) //spring里面自己用得bean,和用戶自定沒有關(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 用戶自定義得bean
- ROLE_SUPPORT 輔助角色
- ROLE_INFRASTRUCTURE 表示完全和用戶得bean沒有關(guān)系,表示一個(gè)在容器里面自己使用得。
到這里就很肯定了,ScheduledAnnotationBeanPostProcessor就是ScheduleTask實(shí)現(xiàn)的重點(diǎn)。
破案了,總結(jié)一下
配置類上標(biāo)注@EnableScheduling注解,@EnableScheduling里面聚合了@Import,@Import最終會(huì)導(dǎo)入一個(gè)ScheduledAnnotationBeanPostProcessor。
ScheduledAnnotationBeanPostProcessor(ScheduleTask實(shí)現(xiàn)的重點(diǎn))
1. ScheduledAnnotationBeanPostProcessor類圖

下面對ScheduledAnnotationBeanPostProcessor實(shí)現(xiàn)的接口逐一說明
- MergedBeanDefinitionPostProcessor是
BeanPostProcessor,在BeanPostProcessor的基礎(chǔ)上增加了postProcessMergedBeanDefinition,這個(gè)接口的主要的實(shí)現(xiàn)類如下,其中最重要的就是AutowiredAnnotationBeanPostProcessor用于處理Autowired。
//在實(shí)例化出來之后,在調(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è)方法,用于判斷是否需要銷毀,和用于銷毀bean的之前調(diào)用。
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
default boolean requiresDestruction(Object bean) {
return true;
}- 實(shí)現(xiàn)了很多的rAware接口,Aware接口沒有什么要說的。
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)聽,范型里面的ContextRefreshedEvent表示關(guān)心的具體事件。- 在整個(gè)spring容器創(chuàng)建好,對象也創(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銷毀的時(shí)候調(diào)用,bean銷毀的時(shí)候的生命周期是,先調(diào)用DestructionAwareBeanPostProcessor#postProcessBeforeDestruction,接著是DisposableBean#destroy方法,后面才是用戶自定義的destroy方法。
2. 針對上面接口幾個(gè)重點(diǎn)方法說明
題外話,定時(shí)任務(wù)大體的實(shí)現(xiàn)是什么?
- 想盡方法拿到被@Schedule修飾的方法。
- 將這些方法上的@Schedule標(biāo)注的解析,保存映射關(guān)系。
- 按照觸發(fā)的條件來調(diào)度定時(shí)任務(wù)。
下面會(huì)根據(jù)這種邏輯來解析Spring中的定時(shí)任務(wù)。
1. 想盡方法拿到被@Schedule修飾的方法。
相關(guān)方法 postProcessAfterInitialization
首先要知道,postProcessAfterInitialization在SpringBean的生命周期中在那一個(gè)環(huán)節(jié),要解視這個(gè)問題,要先知道SpringBean的生命周期是什么?那么這個(gè)就繁瑣了,生命周期的文檔多的是,找一個(gè)看看就可以。簡單的說,就是Spring在初始化完成的最后一步會(huì)調(diào)用postProcessAfterInitialization。當(dāng)然,代理對象的創(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增強(qiáng)之前的原始的類,并且這里也能說明CGLib是通過繼承來實(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è)我覺得并沒有啥子問題,誰在定時(shí)任務(wù)執(zhí)行的時(shí)候需要傳遞參數(shù)
//判斷這個(gè)方法是否是靜態(tài),私有的,
Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
//將需要執(zhí)行的方法封裝成ScheduledMethodRunnable,這個(gè)類實(shí)現(xiàn)很簡單,就倆屬性,
//target表示bean
//method表示需要執(zhí)行的方法
Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
//標(biāo)志位,開始都是false,只要找到@Scheduled注解,并且解析到參數(shù),就是false,后面還會(huì)對他進(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是可以寫SPEL表達(dá)式的。embeddedValueResolver處理器很常見,會(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來處理值,很巧妙。
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的類型不同。其余都是一樣的。
// 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集合
//但是這里的加鎖操作,我沒有看懂,不知道這個(gè)是干嘛的?
//這里會(huì)有并發(fā)的問題嗎?首先他是在postProcessAfterInitialization方法里面起作用的,這個(gè)方法在spring解析bean的時(shí)候起作用的
//并且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());
}
}問題:
上面代碼中加鎖操作我沒有看懂,有大佬能告知我一下。
說明:
對于上面代碼中幾個(gè)重點(diǎn)的類說明
1.ScheduledMethodRunnable
封裝了bean和@Schedule標(biāo)注的方法,將他倆封裝為一個(gè)Runnable。
2.CronTrigger
Trigger表示觸發(fā)器,在@Schedule注解里面每一個(gè)類型都對應(yīng)不同的Trigger。

Trigger里面最核心的方法是`Date nextExecutionTime(TriggerContext triggerContext);` 下一次執(zhí)行的時(shí)間,那么對于Cron或者PeriodTigger都是計(jì)算下一次執(zhí)行的時(shí)間。
3.CronTask
Task表示任務(wù),task不是接口,是一個(gè)類。

task中最核心的方法是getRunnable,TiggerTask在它的基礎(chǔ)上增加了getTrigger,CronTask在之前的基礎(chǔ)上增加了getExpression()
4.ScheduledTask
首先,他是final的
有兩個(gè)屬性值
private final Task task;//任務(wù)
//保留的是ScheduledFuture的引用,ScheduledFuture是scheduledExecutorService提交任務(wù)之后的引用對象。
//ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
// }, 0, 0, TimeUnit.SECONDS);
@Nullable
volatile ScheduledFuture<?> future;總的來說,ScheduledTask保留了task提交給scheduledExecutorServic之后的引用對象和task任務(wù)。
5.ScheduledTaskRegistrar

- InitializingBean 在調(diào)用自定義init方法之前調(diào)用
- DisposableBean 上面說了,在調(diào)用自定義destroy方法之前調(diào)用
- ScheduledTaskHolder,通過這個(gè)接口能拿到所有的ScheduledTask
通過ScheduledTaskRegistrar可以注冊ScheduledTask,也能拿到ScheduledTask,所以,之后的重點(diǎn)就看看ScheduledTaskRegistrar的邏輯
到這里,已經(jīng)完成了從bean中檢索Scheduled修飾的方法,并且解析ScheduledTask注解的屬性,轉(zhuǎn)換為對應(yīng)的Bean,通過ScheduledTaskRegistrar注冊到ScheduledTaskRegistrar里面。
3. 按照觸發(fā)的條件來調(diào)度定時(shí)任務(wù)。(onApplicationEvent)
前面說過,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() {
//如果說在spring中沒有 TaskScheduler的實(shí)現(xiàn)類,也沒有ScheduledExecutorService的實(shí)現(xiàn)類,那就自己默認(rèn)來一個(gè),
// Executors.newSingleThreadScheduledExecutor(); 核心線程是1,但是最大線程數(shù)是Max。
// 通過ConcurrentTaskScheduler包裝。
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
//下面就是挨個(gè)從之前注冊的這些task里面從 ScheduledTaskRegistrar里面維護(hù)的雙向映射關(guān)系中獲取scheduledTask,通過
// ScheduledTaskRegistrar中的taskScheduled提交任務(wù),將返回的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的種類可能不一樣,并且提交個(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ù),接下來就看看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è)流程,下面,在來看看TaskScheduler的相關(guān)東西。最后的任務(wù)肯定都是通過他來執(zhí)行的。重中之重
4. TaskScheduler(重中之重)分析

注意:這個(gè)接口默認(rèn)實(shí)現(xiàn)是ThreadPoolTaskScheduler,下面就針對ThreadPoolTaskScheduler和cronTask來做詳細(xì)的說明
TaskScheduler 任務(wù)的接口里面的詳細(xì)的方法,在這里就不展示了,因?yàn)樘嗔?,這里就用ScheduledFuture<?> schedule(Runnable task, Trigger trigger);來做詳細(xì)的說明

**提示:**默認(rèn)在Spring里面是不會(huì)自動(dòng)創(chuàng)建的,需要手動(dòng)的聲明@Bean,這里要注意,他InitializingBean和DisposableBean。這里面肯定有初始化操作和銷毀操作
繼續(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方法通過將這個(gè)任務(wù)提交給executor,但是這里寫的很巧妙
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)先級是 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接口,并且通過set方法也可以設(shè)置)
- LoggingErrorHandler(默認(rèn))
- PropagatingErrorHandler(打日志,之后報(bào)錯(cuò))
到這里,還差最后一步,就是怎么按照cron表達(dá)式來運(yùn)行定時(shí)任務(wù),并且,到現(xiàn)在為止,沒看到CronTrigger的nextExecutionTime
5. 怎么做調(diào)度(ReschedulingRunnable的具體實(shí)現(xiàn))

首先他是一個(gè)ScheduledFuture。在這里主要看兩個(gè)方法run和schedule,這里的實(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();
//開始跑任務(wù),這里是通過反射調(diào)用的,
//要知道這里的runnable是ScheduledMethodRunnable對象,反射調(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è)類里面直接new出來的
//上次執(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ù)注冊,繼續(xù)循環(huán),
schedule();
}
}
}這兩個(gè)方法涉及的確實(shí)很精妙,避免了間隔的問題,達(dá)到時(shí)間絕對的標(biāo)準(zhǔn)。
定時(shí)任務(wù)還有有一種設(shè)計(jì)方法
1.將注解里面的cron解析出來,然后維護(hù)在一個(gè)地方(內(nèi)存或者redis)
2.有兩個(gè)組件,調(diào)度器和執(zhí)行器,
- 調(diào)度器
- 每間隔多少秒去遍歷所有的任務(wù),看看下次執(zhí)行的時(shí)間是否比當(dāng)前時(shí)間小,如果小,那就說明改執(zhí)行了,就將方法傳遞給執(zhí)行器,執(zhí)行器來執(zhí)行任務(wù),調(diào)度器還和之前一樣,間隔遍歷所有任務(wù)
- 執(zhí)行器
- 執(zhí)行任務(wù)
但是這樣存在一個(gè)問題,比如間隔10秒,但是任務(wù)是需要4秒執(zhí)行一次,這就出現(xiàn)問題了
但是,Spring的這種方式是覺不會(huì)出現(xiàn)這樣的問題,并且這種方式還支持取消任務(wù)。因?yàn)榫S護(hù)了Future引用。
重點(diǎn)
ReschedulingRunnable中的schedule和run方法之前的關(guān)系,如果有一個(gè)任務(wù)每間隔5秒要執(zhí)行一次。
- 開始的時(shí)候是調(diào)用schedule方法,這個(gè)方法里面計(jì)算出下次執(zhí)行的時(shí)間,并且算出當(dāng)前時(shí)間和下次執(zhí)行時(shí)間的差值,然后通過延遲啟動(dòng),就能獲取精確的啟動(dòng)時(shí)間。
- 提交之后就會(huì)運(yùn)行run方法,run方法會(huì)調(diào)用ScheduledMethodRunnable的run方法,ScheduledMethodRunnable里面會(huì)通過反射調(diào)用方法。
- 運(yùn)行完成之后,會(huì)更新Context,保留這次執(zhí)行的相關(guān)信息,然后判斷future是否取消,沒有取消就繼續(xù)調(diào)用schedule方法,本次已經(jīng)執(zhí)行完成,下次的任務(wù)就繼續(xù)開始了。
到這里,關(guān)于Spring中定時(shí)任務(wù)實(shí)現(xiàn)的已經(jīng)結(jié)束了。不禁感嘆,這種實(shí)現(xiàn)方式真的很精妙。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- Spring中@EnableScheduling實(shí)現(xiàn)定時(shí)任務(wù)代碼實(shí)例
- Spring中的@EnableScheduling定時(shí)任務(wù)注解
- SpringBoot注解@EnableScheduling定時(shí)任務(wù)詳細(xì)解析
- SpringBoot使用Scheduling實(shí)現(xiàn)定時(shí)任務(wù)的示例代碼
- springboot通過SchedulingConfigurer實(shí)現(xiàn)多定時(shí)任務(wù)注冊及動(dòng)態(tài)修改執(zhí)行周期(示例詳解)
- springboot項(xiàng)目使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)的案例代碼
- SpringBoot使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)多機(jī)器部署問題(推薦)
- Spring Scheduling本地任務(wù)調(diào)度設(shè)計(jì)與實(shí)現(xiàn)方式
相關(guān)文章
Java利用反射實(shí)現(xiàn)框架類的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Java利用反射實(shí)現(xiàn)框架類的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Springboot hibernate envers使用過程詳解
這篇文章主要介紹了Springboot hibernate envers使用過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
SpringBoot超詳細(xì)講解多數(shù)據(jù)源集成
今天分享下SpringBoot多數(shù)據(jù)源集成,我怕麻煩,這里我覺得我的集成也應(yīng)該是最簡單的,清晰明了2022-05-05
實(shí)例講解Java設(shè)計(jì)模式編程中的OCP開閉原則
這篇文章主要介紹了Java設(shè)計(jì)模式編程中的開閉原則,開閉原則的大意被作者總結(jié)為用抽象構(gòu)建框架,用實(shí)現(xiàn)擴(kuò)展細(xì)節(jié),需要的朋友可以參考下2016-02-02
RedisKey的失效監(jiān)聽器KeyExpirationEventMessageListener問題
這篇文章主要介紹了RedisKey的失效監(jiān)聽器KeyExpirationEventMessageListener問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05

