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

Spring Scheduling本地任務(wù)調(diào)度設(shè)計與實現(xiàn)方式

 更新時間:2024年04月15日 09:19:31   作者:肥肥技術(shù)宅  
這篇文章主要介紹了Spring Scheduling本地任務(wù)調(diào)度設(shè)計與實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、Spring Boot 集成 Scheduling

對于不涉及分布式計算又關(guān)于時間的任務(wù)處理,也就是本地任務(wù)調(diào)度(Task Schduling)既是 Spring Framework 的集成功能,也是 Spring Boot 的重要特性。

在 Spring Boot 中,任務(wù)調(diào)度的使用得到了極大簡化。

1、簡單任務(wù)調(diào)度

從官方文檔介紹中,在任務(wù)調(diào)度類上聲明 EnableScheduling 和 @Configuration,并在調(diào)度方法添加 @Scheduled 就可以完成任務(wù)調(diào)度。

@Configuration
@EnableScheduling
public class SayHelloTask {
 
    @Scheduled(cron = "${hello.schedule.cron:*/10 * * * * *}")
    public void sayHello() {
        System.out.print("Hello,Schedule");
    }
}

@Scheduled 支持 cron 表達式,它的使用在這篇文章中做了介紹。

Spring Boot 做了增強,可以添加默認值,優(yōu)先從配置文件中讀取 hello.schedule.cron,如果為空則使用后面默認的值,也就是每十秒鐘執(zhí)行一次 sayHello 方法。

線程池默認使用一個線程,可使用 spring.task.scheduling 命名空間進行如下微調(diào):

properties復(fù)制代碼spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2

2、自定義任務(wù)調(diào)度

如果需要擴展任務(wù)調(diào)度,可以實現(xiàn) SchedulingConfigurer 來完成。

@Component
public class SayHiSchedulingConfigurer implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(4);
        threadPoolTaskScheduler.setThreadNamePrefix("helloschedules");
        threadPoolTaskScheduler.initialize();
 
        taskRegistrar.setScheduler(threadPoolTaskScheduler);
    }
}

這樣,調(diào)整了任務(wù)調(diào)度的線程池大小,也修改了線程日志名稱,便于日志分析定位。

二、Spring Scheduling 設(shè)計說明

Spring Boot 對 TaskExecution and Scheduling 做了簡要使用說明,深入了解本地任務(wù)調(diào)度可以參考 Spring Framework 對 TaskExecution and Scheduling 的介紹。

下面就以 Spring Boot 3.1.2 以及 Spring Framework 6.0.11 版本為例,重點分析 Spring 對本地任務(wù)調(diào)度的實現(xiàn)方法。

首先,打開 @EnableScheduling 注解:

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

這個注解寫了大段的注釋用于解釋任務(wù)調(diào)度的用法,從注解中我們可以了解到本地調(diào)度的設(shè)計說明,這是后面分析源碼、擴展實現(xiàn)、最佳實踐的基礎(chǔ):

  • 簡單的任務(wù)調(diào)度可以通過在方法上標記 @Scheduled 實現(xiàn),Spring 提供了三種調(diào)度模式 cron、fixedRate 和 fixedDelay,也就是 cron 表達式執(zhí)行、固定頻率執(zhí)行與固定延遲執(zhí)行。
  • Spring 容器會先掃描 org.springframework.scheduling.TaskScheduler 和 java.util.concurrent.ScheduledExecutorService 兩個 bean,如果都沒有注入的話會創(chuàng)建一個默認的單線程任務(wù)調(diào)度器。
  • 如果 @Scheduled 無法滿足任務(wù)調(diào)度配置的話,或者需要運行時配置 fixRate 與 fixDelay,可以通過標記 @Configuration 來實現(xiàn) SchedulingConfigurer,這樣就可以訪問底層的 ScheduledTaskRegistrar 實例,實現(xiàn)細粒度的任務(wù)控制。注意使用類似 @Bean(destroyMethod="shutdown") 來保證自定義任務(wù)執(zhí)行器在 Spring 應(yīng)用程序上下文關(guān)閉時也能夠正確關(guān)閉。
  • 最重要的,EnableScheduling 僅適用于本地應(yīng)用程序上下文,也就是我們說的本地任務(wù)調(diào)度。分布式任務(wù)調(diào)度會有其他解決方案。

三、Spring Scheduling 關(guān)鍵類初探

1、SchedulingConfiguration

使用 @EnableScheduling 就會自動引入這個注解,它只做了一件事就是注入了一個 bean ScheduledAnnotationBeanPostProcessor。

@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();
    }
 
}

2、ScheduledAnnotationBeanPostProcessor

ScheduledAnnotationBeanPostProcessor 是任務(wù)調(diào)度的核心類,由 @EnableScheduling 注解自動注冊,作為 bean 的后置處理器實現(xiàn)了大量接口。核心功能是提供 cron、fixedRate 和 fixedDelay 三種調(diào)度模式,識別 @Scheduled 標記的方法,并轉(zhuǎn)換成 TaskScheduler 可調(diào)度的任務(wù)。以及,識別所有 SchedulingConfigurer 的實例,允許自定義使用調(diào)度任務(wù)或?qū)θ蝿?wù)進行細粒度的控制。

(1)postProcessAfterInitialization

忽略掉細枝末節(jié),方法的核心功能是掃描帶有 @Scheduled 注解的方法,并處理成標準化任務(wù)。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    // ...
    AnnotationUtils.isCandidateClass(targetClass, List.of(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);
    });
    
    // ...
    annotatedMethods.forEach((method, scheduledAnnotations) ->
            scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
 
    // ...
    return bean;
}

(2)processScheduled

將掃描到的所有調(diào)度配置借助 ScheduledTaskRegistrar 轉(zhuǎn)換成標準化調(diào)度任務(wù),直接被異步執(zhí)行,返回結(jié)果交給 ScheduledTask.future。

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);
 
        // ...
 
        // 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.isNegative(), "'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))));
                }
            }
        }
 
        // ...
 
        // Finally register the scheduled tasks
        synchronized (this.scheduledTasks) {
            Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
            regTasks.addAll(tasks);
        }
 
        // ...
}

這里分析一下 fixedDelay 與 fixedRate 的區(qū)別:

fixedDelay 屬性可確保在執(zhí)行任務(wù)的結(jié)束時間與下一次執(zhí)行任務(wù)的開始時間之間有 n 毫秒的延遲。當(dāng)我們需要確保只有一個任務(wù)實例一直在運行時,該屬性特別有用。

而 fixedRate 屬性是每 n 毫秒運行一次計劃任務(wù)。它不會檢查任務(wù)之前的執(zhí)行情況。如果任務(wù)的所有執(zhí)行都是獨立的,這一點就很有用。如果我們不希望超出內(nèi)存和線程池的大小,那么 fixedRate 就會非常方便。不過,如果進入的任務(wù)不能快速完成,就有可能出現(xiàn)內(nèi)存不足異常。

(3)finishRegistration

這個方法完成任務(wù)調(diào)度器注冊。提供自定義調(diào)度任務(wù)擴展點的 SchedulingConfigurer 接口,并被全部掃描進來。

最后,查找 TaskScheduler,添加到 ScheduledTaskRegistrar 中。

private void finishRegistration() {
    if (this.scheduler != null) {
        this.registrar.setScheduler(this.scheduler);
    }
 
    if (this.beanFactory instanceof ListableBeanFactory lbf) {
        Map<String, SchedulingConfigurer> beans = lbf.getBeansOfType(SchedulingConfigurer.class);
        List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
        AnnotationAwareOrderComparator.sort(configurers);
        for (SchedulingConfigurer configurer : configurers) {
            configurer.configureTasks(this.registrar);
        }
    }
    // ...
 
    this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true /false));
    
    // ...
 
    this.registrar.afterPropertiesSet();
}

3、ScheduledTaskRegistrar

ScheduledAnnotationBeanPostProcessor 被 SchedulingConfiguration 創(chuàng)建的時候二話沒說,初始化就創(chuàng)建了一個 ScheduledTaskRegistrar。

ScheduledTaskRegistrar 用于輔助任務(wù)注冊到 TaskScheduler 中,尤其是結(jié)合 @EnableAsync 注解和 SchedulingConfigurer 的回調(diào)方法時。這個 bean 在創(chuàng)建時會先判斷是否存在 TaskScheduler,沒有就會創(chuàng)建一個單線程任務(wù)調(diào)度池,然后逐一把調(diào)度任務(wù)添加到任務(wù)隊列中。

(1)scheduleTasks

在被 ScheduledAnnotationBeanPostProcessor#finishRegistration 調(diào)用時,就是完成任務(wù)標準化,afterPropertiesSet 只完成了一個方法完成,也就是 scheduleTasks。

protected void scheduleTasks() {
    if (this.taskScheduler == null) {
       this.localExecutor = Executors.newSingleThreadScheduledExecutor();
       this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
    }
    
    // ...
    
    if (this.cronTasks != null) {
       for (CronTask task : this.cronTasks) {
          addScheduledTask(scheduleCronTask(task));
       }
    }
    
    // ...
}

(2)scheduleCronTask

使用適配器模式將各個 Task 轉(zhuǎn)換成 ScheduledTask,成為標準化任務(wù)執(zhí)行。

其他幾個方法 scheduleTriggerTask,scheduleFixedRateTask,scheduleFixedDelayTask 都是類似的。

public ScheduledTask scheduleCronTask(CronTask task) {
    ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
    boolean newTask = false;
    if (scheduledTask == null) {
       scheduledTask = new ScheduledTask(task);
       newTask = true;
    }
    if (this.taskScheduler != null) {
       scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
    }
    else {
       addCronTask(task);
       this.unresolvedTasks.put(task, scheduledTask);
    }
    return (newTask ? scheduledTask : null);
}

四、Spring Scheduling 調(diào)用時序

@EnableScheduling 的引入使得 Spring 容器開啟了對本地調(diào)度任務(wù)掃描,并自動裝配了 SchedulingConfiguration,實際上是引入了 ScheduledAnnotationBeanPostProcessor。

ScheduledAnnotationBeanPostProcessor 實現(xiàn)了 postProcessAfterInitialization 首先被執(zhí)行,在 bean 初始化完成后對 @Scheduled 注解進行掃描并轉(zhuǎn)換成標準化本地調(diào)度任務(wù) ScheduledTask。

任務(wù)轉(zhuǎn)換完成后就會異步執(zhí)行任務(wù),但是需要等待主線程對線程池的初始化。ScheduledAnnotationBeanPostProcessor 實現(xiàn)了 onApplicationEvent 完成對任務(wù)注冊的初始化,包括自定義調(diào)度任務(wù)配置

SchedulingConfigurer 的所有實現(xiàn)的掃描,以及對調(diào)度任務(wù)執(zhí)行者 TaskScheduler 的初始化,如果都沒有注入的話會創(chuàng)建一個默認的單線程任務(wù)調(diào)度器。

最后,準備工作完成后,由 ScheduledTaskRegistrar 的各類型調(diào)度任務(wù)分別調(diào)用 ScheduledFuture 的 schedule 方法完成任務(wù)。

總結(jié)

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

相關(guān)文章

  • 如何修改json字符串中某個key對應(yīng)的value值

    如何修改json字符串中某個key對應(yīng)的value值

    這篇文章主要介紹了如何修改json字符串中某個key對應(yīng)的value值操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • Java回調(diào)函數(shù)原理實例與代理模式的區(qū)別講解

    Java回調(diào)函數(shù)原理實例與代理模式的區(qū)別講解

    今天小編就為大家分享一篇關(guān)于Java回調(diào)函數(shù)原理實例與代理模式的區(qū)別講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • java中使用try-catch-finally一些值得注意的事(必看)

    java中使用try-catch-finally一些值得注意的事(必看)

    下面小編就為大家?guī)硪黄猨ava中使用try-catch-finally一些值得注意的事(必看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08
  • Java編程使用箱式布局管理器示例【基于swing組件】

    Java編程使用箱式布局管理器示例【基于swing組件】

    這篇文章主要介紹了Java編程使用箱式布局管理器,結(jié)合實例形式分析了基于swing組件的箱式布局管理器定義與使用技巧,需要的朋友可以參考下
    2018-01-01
  • Spring Boot中是如何處理日期時間格式的

    Spring Boot中是如何處理日期時間格式的

    這篇文章主要介紹了Spring Boot中是如何處理日期時間格式的,幫助大家更好的理解和學(xué)習(xí)spring boot框架,感興趣的朋友可以了解下
    2020-11-11
  • 解決springboot項目上傳文件出現(xiàn)臨時文件目錄為空的問題

    解決springboot項目上傳文件出現(xiàn)臨時文件目錄為空的問題

    這篇文章主要介紹了解決springboot項目上傳文件出現(xiàn)臨時文件目錄為空的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java中StringBuffer和StringBuilder_動力節(jié)點Java學(xué)院整理

    Java中StringBuffer和StringBuilder_動力節(jié)點Java學(xué)院整理

    StringBuffer、StringBuilder和String一樣,也用來代表字符串。String類是不可變類,StringBuffer則是可變類,任何對它所指代的字符串的改變都不會產(chǎn)生新的對象。本文重點給大家介紹String、StringBuffer、StringBuilder區(qū)別,感興趣的朋友一起看看吧
    2017-04-04
  • SpringBoot登錄攔截配置詳解(實測可用)

    SpringBoot登錄攔截配置詳解(實測可用)

    這篇文章主要介紹了SpringBoot登錄攔截配置詳解(實測可用),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Spring Cloud Alibaba教程之Sentinel的使用

    Spring Cloud Alibaba教程之Sentinel的使用

    這篇文章主要介紹了Spring Cloud Alibaba教程之Sentinel的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • java 多態(tài)性詳解及簡單實例

    java 多態(tài)性詳解及簡單實例

    這篇文章主要介紹了java 多態(tài)性詳解及簡單實例的相關(guān)資料,需要的朋友可以參考下
    2017-02-02

最新評論