Spring調(diào)度框架EnableScheduling&Scheduled源碼解析
前言
在實際項目開發(fā)中,有時會遇到定時調(diào)度的開發(fā)需要,這部分的功能在Spring框架中給出了較好的支持,即@EnableScheduling&Scheduled定時調(diào)度框架,本著不僅知其然還要知其所以然的指導(dǎo)思想,下面對該調(diào)度框架進行源碼解析,以便更好的理解其執(zhí)行過程;
1.開啟調(diào)度框架
Spring框架中,為了開啟調(diào)度框架功能,需要在配置類上標(biāo)注@EnableScheduling注解,這也是Spring中Enable*模式的典型應(yīng)用,下面看一下@EnableScheduling的具體實現(xiàn):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}這里通過@Import注解,導(dǎo)入了配置類SchedulingConfiguration,進一步看下SchedulingConfiguration配置類的源碼,如下:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}可以看到,這里定義了一個Bean后處理器ScheduledAnnotationBeanPostProcessor,調(diào)度框架的解析邏輯也是定義在ScheduledAnnotationBeanPostProcessor中的,下面著重對該部分進行具體分析;
2.ScheduledAnnotationBeanPostProcessor Bean后處理器分析
Bean后處理器中,主要分析下后處理器的攔截方法,如下:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : 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, scheduledAnnotations) ->
scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}如上,postProcessAfterInitialization方法中,主要對標(biāo)注@Scheduled和聚合注解@Schedules的類成員方法進行處理,主要分為2步:
1)識別標(biāo)注@Scheduled和聚合注解@Schedules的方法;
2)對注解方法調(diào)用processScheduled方法進行處理;
方法processScheduled處理過程如下:
/**
* Process the given {@code @Scheduled} method declaration on the given bean.
* @param scheduled the {@code @Scheduled} annotation
* @param method the method that the annotation has been declared on
* @param bean the target bean instance
* @see #createRunnable(Object, Method)
*/
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
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);
// Determine initial delay
long initialDelay = convertToMillis(scheduled.initialDelay(), scheduled.timeUnit());
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 = convertToMillis(initialDelayString, scheduled.timeUnit());
}
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 = convertToMillis(scheduled.fixedDelay(), scheduled.timeUnit());
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 = convertToMillis(fixedDelayString, scheduled.timeUnit());
}
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 = convertToMillis(scheduled.fixedRate(), scheduled.timeUnit());
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 = convertToMillis(fixedRateString, scheduled.timeUnit());
}
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)將調(diào)用目標(biāo)方法的過程包裝為ScheduledMethodRunnable類
2)構(gòu)造CronTask并進行調(diào)度
3)構(gòu)造FixedDelayTask并進行調(diào)度
4)構(gòu)造FixedRateTask并進行調(diào)度
下面主要說明下調(diào)度任務(wù)的類型以及具體的調(diào)度方法;
2.1 調(diào)度框架支持的Task類型

Spring調(diào)度框架中重要支持3種調(diào)度任務(wù)類型(繼承結(jié)構(gòu)如上圖),具體說明如下:
1)CronTask:cron表達式調(diào)度的任務(wù)
2)FixedDelayTask:固定延遲時間執(zhí)行的任務(wù)
3)FixedRateTask:固定速率執(zhí)行的任務(wù)
2.2 對Task進行調(diào)度執(zhí)行
上述3種的調(diào)度執(zhí)行實現(xiàn)近似,下面以FixedDelayTask進行說明,該任務(wù)的調(diào)度方法為scheduleFixedDelayTask,具體實現(xiàn)如下:
/**
* Schedule the specified fixed-delay task, either right away if possible
* or on initialization of the scheduler.
* @return a handle to the scheduled task, allowing to cancel it
* (or {@code null} if processing a previously registered task)
* @since 5.0.2
*/
@Nullable
public ScheduledTask scheduleFixedDelayTask(FixedDelayTask 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(this.taskScheduler.getClock().millis() + task.getInitialDelay());
scheduledTask.future =
this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval());
}
else {
scheduledTask.future =
this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), task.getInterval());
}
}
else {
addFixedDelayTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}這里主要包含以下幾步:
1)將調(diào)度任務(wù)包裝為ScheduledTask類型,其中封裝了執(zhí)行結(jié)果ScheduledFuture
2)存在任務(wù)調(diào)度器(taskScheduler)時,直接進行調(diào)度執(zhí)行
3)不存在任務(wù)調(diào)度器(taskScheduler)時,將任務(wù)暫存到fixedDelayTasks中,待調(diào)用afterPropertiesSet方法時再進行調(diào)度執(zhí)行
3.任務(wù)調(diào)度器
3.1 任務(wù)調(diào)度器獲取
任務(wù)調(diào)度器支持自定義,當(dāng)無自定義調(diào)度器時,調(diào)度框架提供了默認(rèn)的任務(wù)調(diào)度器;
自定義任務(wù)調(diào)度器的處理邏輯在方法finishRegistration中,如下:
private void finishRegistration() {
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
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);
}
}
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
try {
// Search for TaskScheduler bean...
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
}
catch (NoUniqueBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +
ex.getMessage());
}
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskScheduler bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +
ex.getMessage());
}
// Search for ScheduledExecutorService bean next...
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +
ex2.getMessage());
}
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
if (logger.isInfoEnabled()) {
logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex2.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +
ex2.getMessage());
}
// Giving up -> falling back to default scheduler within the registrar...
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
this.registrar.afterPropertiesSet();
}上述獲取任務(wù)調(diào)度器的優(yōu)先級順序為:
1)當(dāng)Bean后處理器中定義了任務(wù)調(diào)度器時,優(yōu)先取Bean后處理器的任務(wù)調(diào)度器
2)在BeanFactory中獲取Bean類型為SchedulingConfigurer的實例,在其方法configureTasks中可以自定義任務(wù)調(diào)度器
3)獲取BeanFactory中TaskScheduler類型的bean(如有)
4)獲取BeanFactory中ScheduledExecutorService類型的bean(如有)
5)當(dāng)上述方式獲取的任務(wù)調(diào)度器都不存在時,會使用框架中默認(rèn)的任務(wù)調(diào)度器,如下:
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}3.2 框架內(nèi)提供的任務(wù)調(diào)度器
框架內(nèi)提供的任務(wù)調(diào)度器主要包括:
1)ConcurrentTaskScheduler
2)ThreadPoolTaskScheduler
繼承結(jié)構(gòu)如下:

3.3 任務(wù)調(diào)度器執(zhí)行邏輯
以上述框架默認(rèn)的ConcurrentTaskScheduler進行說明,在調(diào)用調(diào)度器方法scheduleWithFixedDelay執(zhí)行時,具體執(zhí)行邏輯為:
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
long initialDelay = startTime.getTime() - this.clock.millis();
try {
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay, delay, TimeUnit.MILLISECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
}
}這里主要包含2部分:
1)首先把task任務(wù)包裝為DelegatingErrorHandlingRunnable類型(支持嵌入錯誤處理器邏輯),具體是在方法decorateTask中實現(xiàn)的,如下:
private Runnable decorateTask(Runnable task, boolean isRepeatingTask) {
Runnable result = TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, isRepeatingTask);
if (this.enterpriseConcurrentScheduler) {
result = ManagedTaskBuilder.buildManagedTask(result, task.toString());
}
return result;
}
public static DelegatingErrorHandlingRunnable decorateTaskWithErrorHandler(
Runnable task, @Nullable ErrorHandler errorHandler, boolean isRepeatingTask) {
if (task instanceof DelegatingErrorHandlingRunnable) {
return (DelegatingErrorHandlingRunnable) task;
}
ErrorHandler eh = (errorHandler != null ? errorHandler : getDefaultErrorHandler(isRepeatingTask));
return new DelegatingErrorHandlingRunnable(task, eh);
}2)調(diào)用線程池方法scheduleWithFixedDelay進行調(diào)度執(zhí)行
至此,Spring調(diào)度框架整體的處理過程總結(jié)如下:
開啟調(diào)度框架(@EnableScheduling)利用bean后處理器識別@Scheduled注解,并包裝為Task任務(wù)利用任務(wù)調(diào)度器(TaskScheduler,自定義或框架默認(rèn))進行調(diào)度執(zhí)行
到此這篇關(guān)于Spring調(diào)度框架EnableScheduling&Scheduled源碼解析的文章就介紹到這了,更多相關(guān)EnableScheduling&Scheduled源碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實現(xiàn)數(shù)據(jù)庫主鍵生成示例
這篇文章主要介紹了java實現(xiàn)數(shù)據(jù)庫主鍵生成示例,需要的朋友可以參考下2014-03-03
Springboot如何優(yōu)雅高效的清除Redis中的業(yè)務(wù)key
這篇文章主要為大家詳細(xì)介紹了Springboot如何優(yōu)雅高效的清除Redis中的業(yè)務(wù)key,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-05-05
Springboot實現(xiàn)動態(tài)定時任務(wù)流程詳解
通過重寫SchedulingConfigurer方法實現(xiàn)對定時任務(wù)的操作,單次執(zhí)行、停止、啟動三個主要的基本功能,動態(tài)的從數(shù)據(jù)庫中獲取配置的定時任務(wù)cron信息,通過反射的方式靈活定位到具體的類與方法中2022-09-09
SpringSceurity實現(xiàn)短信驗證碼功能的示例代碼
這篇文章主要介紹了SpringSceurity實現(xiàn)短信驗證碼功能的示例代碼,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
Springboot獲取文件內(nèi)容如何將MultipartFile轉(zhuǎn)File
本文給大家介紹Springboot獲取文件內(nèi)容,將MultipartFile轉(zhuǎn)File方法,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-01-01
SpringBoot使用Spring-Data-Jpa實現(xiàn)CRUD操作
這篇文章主要為大家詳細(xì)介紹了SpringBoot使用Spring-Data-Jpa實現(xiàn)CRUD操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-08-08
JavaSE API實現(xiàn)生成隨機數(shù)的2種方法(Random類和Math類的Random方法)
本文主要介紹了JavaSE API實現(xiàn)生成隨機數(shù)的2種方法,主要包括Random類和Math類的random方法都可以用來生成隨機數(shù),具有一定的參考價值,感興趣的可以了解一下2023-10-10

