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

一文帶你掌握SpringBoot中常見定時(shí)任務(wù)的實(shí)現(xiàn)

 更新時(shí)間:2024年03月07日 09:47:25   作者:碼農(nóng)Academy  
這篇文章主要為大家詳細(xì)介紹了Spring?Boot中定時(shí)任務(wù)的基本用法、高級特性以及最佳實(shí)踐,幫助開發(fā)人員更好地理解和應(yīng)用定時(shí)任務(wù),提高系統(tǒng)的穩(wěn)定性和可靠性,需要的可以參考下

引言

在現(xiàn)代軟件開發(fā)中,定時(shí)任務(wù)是一種常見的需求,用于執(zhí)行周期性的任務(wù)或在特定的時(shí)間點(diǎn)執(zhí)行任務(wù)。這些任務(wù)可能涉及數(shù)據(jù)同步、數(shù)據(jù)備份、報(bào)表生成、緩存刷新等方面,對系統(tǒng)的穩(wěn)定性和可靠性有著重要的影響。Spring Boot提供了強(qiáng)大且簡單的定時(shí)任務(wù)功能,使開發(fā)人員能夠輕松地管理和執(zhí)行這些任務(wù)。

本文將介紹 Spring Boot中定時(shí)任務(wù)的基本用法、高級特性以及最佳實(shí)踐,幫助開發(fā)人員更好地理解和應(yīng)用定時(shí)任務(wù),提高系統(tǒng)的穩(wěn)定性和可靠性。

SpringBoot中的定時(shí)任務(wù)

SpringBoot中的定時(shí)任務(wù)主要通過@Scheduled注解以及SchedulingConfigurer接口實(shí)現(xiàn)。

@Scheduled注解

@Scheduled注解是Spring提供的一個(gè)注解,用于標(biāo)記方法作為定時(shí)任務(wù)執(zhí)行。通過 @Scheduled注解,開發(fā)人員可以輕松地配置方法在指定的時(shí)間間隔或時(shí)間點(diǎn)執(zhí)行,實(shí)現(xiàn)各種定時(shí)任務(wù)需求。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Repeatable(Schedules.class)  
public @interface Scheduled {

	String cron() default "";

	long fixedDelay() default -1;

	long fixedRate() default -1;

	long initialDelay() default -1;
}

以上為@Scheduled源碼中關(guān)鍵屬性,各屬性含義如下:

cron: 接受標(biāo)準(zhǔn)的Unix Cron表達(dá)式,用于定義復(fù)雜的計(jì)劃執(zhí)行時(shí)間。

/**  
* cron屬性可以設(shè)置指定時(shí)間執(zhí)行,cron表達(dá)式跟linux一樣  
*/  
@Scheduled(cron = "0 45 14 ? * *")  
public void fixTimeExecution() {  
	System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行");  
}

fixedRate: 以固定的頻率執(zhí)行任務(wù),指定兩次執(zhí)行之間的間隔時(shí)間(單位是毫秒)。

/**  
* fixedRate屬性設(shè)置每隔固定時(shí)間執(zhí)行  
*/  
@Scheduled(fixedRate = 5000)  
public void reportCurrentTime() {  
	System.out.println("每隔五秒執(zhí)行一次" + dateFormat.format(new Date())); 
}

fixedDelay:在每次任務(wù)完成后等待一定的時(shí)間再進(jìn)行下一次執(zhí)行,指定連續(xù)執(zhí)行之間的延遲時(shí)間。

/**  
* 上一次任務(wù)執(zhí)行完成之后10秒后在執(zhí)行  
*/  
@Scheduled(fixedDelay = 10000)  
public void runWithFixedDelay() {  
	System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行");  
}

initialDelay:首次執(zhí)行前的延遲時(shí)間。

/**  
* 初始延遲1秒后開始,然后每10秒執(zhí)行一次  
*/  
@Scheduled(initialDelay=1000, fixedDelay=10000)  
public void executeWithInitialAndFixedDelay() {  
	System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行");  
}

這里要注意fixedRate與fixedDelay的區(qū)別:fixedRate是基于任務(wù)開始執(zhí)行的時(shí)間點(diǎn)來計(jì)算下一次任務(wù)開始執(zhí)行的時(shí)間,因此任務(wù)的執(zhí)行時(shí)間間隔是相對固定的,不受到任務(wù)執(zhí)行時(shí)間的影響。如果指定的時(shí)間間隔小于任務(wù)執(zhí)行的實(shí)際時(shí)間,則任務(wù)可能會并發(fā)執(zhí)行。而fixedDelay是基于任務(wù)執(zhí)行完成的時(shí)間點(diǎn)來計(jì)算下一次任務(wù)開始執(zhí)行的時(shí)間,因此任務(wù)的執(zhí)行時(shí)間間隔是相對不規(guī)則的,受到任務(wù)執(zhí)行時(shí)間的影響。

SpringBoot支持同時(shí)定義多個(gè)定時(shí)任務(wù)方法,每個(gè)方法可以使用不同的參數(shù)配置,以滿足不同的定時(shí)任務(wù)需求。同時(shí),我們必須在配置類中使用@EnableScheduling注解開啟定時(shí)任務(wù)。

@Configuration  
@EnableScheduling  
public class ScheduledTaskConfig { 

}

或者

@EnableScheduling  
@SpringBootApplication  
public class SpringBootBaseApplication {  
  
    public static void main(String[] args) {  
       SpringApplication.run(SpringBootBaseApplication.class, args);  
    }  
}

在SpringBoot應(yīng)用程序中,除了在代碼中使用注解配置定時(shí)任務(wù)外,還可以通過配置文件來配置定時(shí)任務(wù)的執(zhí)行規(guī)則。這種方式更加靈活,可以在不修改源代碼的情況下,動(dòng)態(tài)調(diào)整定時(shí)任務(wù)的執(zhí)行規(guī)則。比如我們在application.properties中配置@Scheduled的屬性:

custom.scheduled.cron = 0/5 * * * * ?  
custom.scheduled.fixedRate=5000  
custom.scheduled.fixedDelay=10000  
custom.scheduled.initialDelay=1000

然后在@Scheduled的方法使用屬性配置定時(shí)任務(wù)執(zhí)行頻率。

@Service  
public class DemoScheduledTaskService {  
  
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");  
  
    /**  
     * fixedRate屬性設(shè)置每隔固定時(shí)間執(zhí)行  
     */  
    @Scheduled(fixedRateString = "${custom.scheduled.fixedRate}")  
    public void reportCurrentTime() {  
        System.out.println("每隔五秒執(zhí)行一次" + dateFormat.format(new Date()));  
    }  
  
    /**  
     * cron屬性可以設(shè)置指定時(shí)間執(zhí)行,cron表達(dá)式跟linux一樣  
     */  
    @Scheduled(cron = "${custom.scheduled.cron}")  
    public void fixTimeExecution() {  
        System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行");  
    }  
  
    /**  
     * 上一次任務(wù)執(zhí)行完成之后10秒后在執(zhí)行  
     */  
    @Scheduled(fixedDelayString = "${custom.scheduled.fixedDelay}")  
    public void runWithFixedDelay() {  
        System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行");  
    }  
  
    /**  
     * 初始延遲1秒后開始,然后每10秒執(zhí)行一次  
     */  
    @Scheduled(initialDelayString = "${custom.scheduled.initialDelay}", fixedDelayString = "${custom.scheduled.fixedDelay}")  
    public void executeWithInitialAndFixedDelay() {  
        System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行");  
    }  
}

注意,這里使用屬性來指定任務(wù)執(zhí)行頻率時(shí),要通過@Scheduled的fixedRateString、fixedDelayString、initialDelayString三個(gè)可以指定字符串的值的屬性去指定,效果等同于long類型的屬性。

通過配置文件配置定時(shí)任務(wù)具有很高的靈活性,可以在不重新編譯和部署應(yīng)用程序的情況下,隨時(shí)調(diào)整定時(shí)任務(wù)的執(zhí)行規(guī)則。同時(shí),也可以根據(jù)不同的環(huán)境(例如開發(fā)、測試、生產(chǎn))配置不同的定時(shí)任務(wù)規(guī)則,以滿足不同環(huán)境下的需求。這種方式可以有效地解耦定時(shí)任務(wù)的配置和業(yè)務(wù)代碼,提高系統(tǒng)的靈活性和可維護(hù)性。

另外,如果希望定時(shí)任務(wù)能夠異步執(zhí)行,不阻塞主線程,可以在方法上同時(shí)加上@Async注解,這樣各任務(wù)就可以異步執(zhí)行了。有關(guān)SpringBoot中使用@Async的講解,請移步:

雖然 @Scheduled 注解是一個(gè)方便的方式來定義定時(shí)任務(wù),但它也存在一些弊端。因?yàn)槿蝿?wù)的執(zhí)行計(jì)劃(如cron表達(dá)式)在編譯時(shí)被硬編碼,因此無法在運(yùn)行時(shí)動(dòng)態(tài)修改,除非重新部署。此外,@Scheduled注解對于配置不同的調(diào)度策略(如使用不同的線程池)顯得力不從心,而且默認(rèn)情況下,@Scheduled任務(wù)在單線程環(huán)境下執(zhí)行,可能出現(xiàn)任務(wù)堆積的情況,尤其在任務(wù)量大或任務(wù)執(zhí)行時(shí)間長的情況下,而且這些任務(wù)可能會變得混亂和難以管理。定時(shí)任務(wù)的配置分散在各個(gè)任務(wù)方法中,不利于統(tǒng)一管理和維護(hù)。對于需要根據(jù)動(dòng)態(tài)條件創(chuàng)建或銷毀定時(shí)任務(wù)的情況,@Scheduled注解也無法滿足需求。

為了解決這些問題,可以使用SchedulingConfigurer接口來動(dòng)態(tài)地創(chuàng)建和管理定時(shí)任務(wù)。通過實(shí)現(xiàn) SchedulingConfigurer 接口,我們可以編寫代碼來動(dòng)態(tài)地注冊和管理定時(shí)任務(wù),從而實(shí)現(xiàn)靈活的任務(wù)調(diào)度需求。接下來,我們將介紹如何使用SchedulingConfigurer接口來創(chuàng)建定時(shí)任務(wù)。

SchedulingConfigurer接口

SchedulingConfigurer 接口是 Spring 提供的一個(gè)用于定時(shí)任務(wù)配置的擴(kuò)展接口,它允許開發(fā)人員更細(xì)粒度地控制定時(shí)任務(wù)的執(zhí)行。通過實(shí)現(xiàn)SchedulingConfigurer接口,可以自定義任務(wù)調(diào)度器(TaskScheduler),配置線程池等參數(shù),以滿足不同場景下的定時(shí)任務(wù)需求。

@Configuration  
@EnableScheduling  
public class CustomSchedulingConfig implements SchedulingConfigurer {

	@Override  
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		// 定時(shí)任務(wù)邏輯
	}
}

通過實(shí)現(xiàn)SchedulingConfigurer接口,重寫configureTasks方法,自定義任務(wù)調(diào)度器的配置。此外我們還可以配置線程池,用于控制定時(shí)任務(wù)執(zhí)行時(shí)的線程數(shù)量、并發(fā)性等參數(shù)。

@Bean(destroyMethod = "shutdown")  
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {  
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();  
    scheduler.setPoolSize(5); // 設(shè)置線程池大小  
    scheduler.setThreadNamePrefix("scheduled-task-"); // 設(shè)置線程名稱前綴  
    scheduler.setAwaitTerminationSeconds(60); // 設(shè)置終止等待時(shí)間  
	// 設(shè)置處理拒絕執(zhí)行的任務(wù)異常
	scheduler.setRejectedExecutionHandler((r, executor) -> log.error("Task rejected", r));
	// 處理定時(shí)任務(wù)執(zhí)行過程中拋出的未捕獲異常
	scheduler.setErrorHandler(e -> log.error("Error in scheduled task", e));
    return scheduler;  
}

然后將自定義的ThreadPoolTaskScheduler設(shè)置到ScheduledTaskRegistrar中去:

@Override  
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
	// 定時(shí)任務(wù)邏輯
	taskRegistrar.setTaskScheduler(threadPoolTaskScheduler());
}

有關(guān)線程池的配置參數(shù)講解,請移步:

通過SchedulingConfigurer接口,可以更靈活地配置任務(wù)調(diào)度器和定時(shí)任務(wù)的執(zhí)行規(guī)則,比如動(dòng)態(tài)注冊定時(shí)任務(wù)、動(dòng)態(tài)修改任務(wù)執(zhí)行規(guī)則等。

動(dòng)態(tài)添加定時(shí)任務(wù)

SchedulingConfigurerconfigureTasks方法中,我們可以根據(jù)業(yè)務(wù)需求,從數(shù)據(jù)庫、配置文件或其它動(dòng)態(tài)來源獲取定時(shí)任務(wù)的信息(如Cron表達(dá)式、任務(wù)執(zhí)行類等),然后創(chuàng)建對應(yīng)的RunnableCallable實(shí)例,并結(jié)合Trigger(如CronTrigger)將其添加到調(diào)度器中。相比@Scheduled注解,這種方式能夠在應(yīng)用運(yùn)行時(shí)隨時(shí)添加新的定時(shí)任務(wù)。

@Override  
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {  
    ThreadPoolTaskScheduler scheduler = threadPoolTaskScheduler();  
    taskRegistrar.setTaskScheduler(scheduler);  
  
    List<CronTaskInfo> tasksFromDB = listTasksFromDatabase();  
  
    for (CronTaskInfo task : tasksFromDB) {  
        Runnable taskRunner = new MyTaskExecutor(task.getTaskData());  
        CronTrigger cronTrigger = new CronTrigger(task.getCronExpression());  
        scheduler.schedule(taskRunner, cronTrigger);  
    }  
}

關(guān)于這里在應(yīng)用運(yùn)行時(shí),動(dòng)態(tài)的添加新的任務(wù),我們可以通過事件驅(qū)動(dòng),輪訓(xùn)檢查,消息隊(duì)列等多種方式,監(jiān)聽到數(shù)據(jù)庫中或者配置文件中新增任務(wù)信息,然后通過SchedulingConfigurer接口動(dòng)態(tài)創(chuàng)建定時(shí)任務(wù)。而這種方式是@Scheduled注解做不到的。

修改定時(shí)任務(wù)規(guī)則

當(dāng)任務(wù)的執(zhí)行規(guī)則需要?jiǎng)討B(tài)變更時(shí),同樣可以在configureTasks方法中實(shí)現(xiàn)。例如,從數(shù)據(jù)庫獲取最新的Cron表達(dá)式,然后取消當(dāng)前任務(wù)并重新添加新的任務(wù)實(shí)例。需要注意的是,取消已有任務(wù)通常需要持有對該任務(wù)的引用,例如使用Scheduler提供的unschedule方法。

// 假設(shè)我們有一個(gè)方法用于獲取更新后的任務(wù)信息  
CronTaskInfo updatedTask = getUpdatedTaskInfoFromDatabase();  
  
// 取消舊的任務(wù)(需要知道舊任務(wù)的TriggerKey)  
TriggerKey triggerKey = ...; // 獲取舊任務(wù)的TriggerKey  
scheduler.unschedule(triggerKey);  
  
// 創(chuàng)建新任務(wù)并設(shè)置新的Cron表達(dá)式  
MyTaskExecutor taskExecutor = new MyTaskExecutor(updatedTask.getTaskData());  
CronTrigger updatedCronTrigger = new CronTrigger(updatedTask.getCronExpression());  
  
// 重新調(diào)度新任務(wù)  
scheduler.schedule(taskRunner, updatedCronTrigger);

另外,我們還可以通過添加任務(wù)時(shí)對其排序或設(shè)置優(yōu)先級等方式間接實(shí)現(xiàn)設(shè)置定時(shí)任務(wù)的執(zhí)行順序。

通過實(shí)現(xiàn)SchedulingConfigurer接口,我們可以擁有對定時(shí)任務(wù)調(diào)度的更多控制權(quán),比如自定義線程池、動(dòng)態(tài)添加任務(wù)以及調(diào)整任務(wù)執(zhí)行策略。這種靈活性使得在復(fù)雜環(huán)境下,特別是需要?jiǎng)討B(tài)管理定時(shí)任務(wù)時(shí),SchedulingConfigurer成為了理想的選擇。

其他第三方任務(wù)調(diào)度框架

除了使用Spring框架提供的 @Scheduled 注解和SchedulingConfigurer接口外,還有許多第三方的任務(wù)調(diào)度庫可供選擇。這些庫通常提供了更多的功能和靈活性,以滿足各種復(fù)雜的任務(wù)調(diào)度需求。以下是一些常見的第三方任務(wù)調(diào)度庫:

Quartz Scheduler

Quartz是一個(gè)功能強(qiáng)大且靈活的任務(wù)調(diào)度庫,具有豐富的功能,如支持基于cron表達(dá)式的任務(wù)調(diào)度、集群支持、作業(yè)持久化等。它可以與Spring框架集成,并且被廣泛應(yīng)用于各種類型的任務(wù)調(diào)度應(yīng)用程序中。

Elastic Job

Elastic Job是一個(gè)分布式任務(wù)調(diào)度框架,可以輕松實(shí)現(xiàn)分布式任務(wù)調(diào)度和作業(yè)執(zhí)行。它提供了分布式任務(wù)執(zhí)行、作業(yè)依賴關(guān)系、作業(yè)分片等功能,適用于大規(guī)模的分布式任務(wù)調(diào)度場景。

xxl-job

xxl-job是一個(gè)分布式任務(wù)調(diào)度平臺,提供了可視化的任務(wù)管理界面和多種任務(wù)調(diào)度方式,如單機(jī)任務(wù)、分布式任務(wù)、定時(shí)任務(wù)等。它支持任務(wù)執(zhí)行日志、任務(wù)失敗重試、動(dòng)態(tài)調(diào)整任務(wù)執(zhí)行策略等功能。

PowerJob

PowerJob是一個(gè)開源的分布式任務(wù)調(diào)度框架,由阿里巴巴集團(tuán)開發(fā)并開源。PowerJob 提供了分布式、高可用的任務(wù)調(diào)度能力,支持多種任務(wù)類型,如定時(shí)任務(wù)、延時(shí)任務(wù)、流程任務(wù)等。

總結(jié)

定時(shí)任務(wù)在現(xiàn)代軟件開發(fā)中扮演著重要的角色,它們可以自動(dòng)化執(zhí)行各種重復(fù)性的任務(wù),提高系統(tǒng)的效率和可靠性。SpringBoot提供了強(qiáng)大而靈活的定時(shí)任務(wù)功能,使我們能夠輕松地管理和執(zhí)行各種定時(shí)任務(wù)。通過@Scheduled注解和SchedulingConfigurer接口,我們可以根據(jù)需求配置定時(shí)任務(wù)的執(zhí)行規(guī)則,實(shí)現(xiàn)各種復(fù)雜的定時(shí)任務(wù)調(diào)度需求。我們可以充分利用SpringBoot中的定時(shí)任務(wù)功能,提高系統(tǒng)的穩(wěn)定性和可靠性,從而更好地滿足業(yè)務(wù)需求。

到此這篇關(guān)于一文帶你掌握SpringBoot中常見定時(shí)任務(wù)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot定時(shí)任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入理解Java 線程通信

    深入理解Java 線程通信

    這篇文章主要介紹了Java 線程通信的的相關(guān)資料,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • Java對象的XML序列化與反序列化實(shí)例解析

    Java對象的XML序列化與反序列化實(shí)例解析

    這篇文章主要介紹了Java對象的XML序列化與反序列化實(shí)例解析,小編覺得還是挺不錯(cuò)的,這里分享給大家。
    2017-10-10
  • java中的switch case語句使用詳解

    java中的switch case語句使用詳解

    這篇文章主要介紹了java中的switch case語句使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Java導(dǎo)入、導(dǎo)出excel用法步驟保姆級教程(附封裝好的工具類)

    Java導(dǎo)入、導(dǎo)出excel用法步驟保姆級教程(附封裝好的工具類)

    這篇文章主要介紹了Java導(dǎo)入、導(dǎo)出excel的相關(guān)資料,講解了使用Java和ApachePOI庫將數(shù)據(jù)導(dǎo)出為Excel文件,包括創(chuàng)建工作簿、工作表、行和單元格,設(shè)置樣式和字體,合并單元格,添加公式和下拉選擇等功能,需要的朋友可以參考下
    2025-03-03
  • Java中資源加載的方法及Spring的ResourceLoader應(yīng)用小結(jié)

    Java中資源加載的方法及Spring的ResourceLoader應(yīng)用小結(jié)

    在Java開發(fā)中,資源加載是一個(gè)基礎(chǔ)而重要的操作,這篇文章主要介紹了深入理解Java中資源加載的方法及Spring的ResourceLoader應(yīng)用,本文通過實(shí)例代碼演示了通過ClassLoader和Class獲取資源的內(nèi)容,以及使用Spring的ResourceLoader加載多個(gè)資源的過程,需要的朋友可以參考下
    2024-01-01
  • kaptcha驗(yàn)證碼使用方法詳解

    kaptcha驗(yàn)證碼使用方法詳解

    這篇文章主要為大家詳細(xì)介紹了kaptcha驗(yàn)證碼的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • Java結(jié)合Kotlin實(shí)現(xiàn)寶寶年齡計(jì)算

    Java結(jié)合Kotlin實(shí)現(xiàn)寶寶年齡計(jì)算

    這篇文章主要為大家介紹了Java結(jié)合Kotlin實(shí)現(xiàn)寶寶年齡計(jì)算示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • java日志門面之JCL和SLF4J詳解

    java日志門面之JCL和SLF4J詳解

    這篇文章主要給大家介紹了關(guān)于java日志門面之JCL和SLF4J的相關(guān)資料,在系統(tǒng)開發(fā)過程中日志框架的選擇與更換是一大挑戰(zhàn),日志門面的概念,如JCL和SLF4J,允許開發(fā)者面向接口編程,文中介紹的非常詳細(xì),需要的朋友可以參考下
    2024-10-10
  • springcloud項(xiàng)目快速開始起始模板的實(shí)現(xiàn)

    springcloud項(xiàng)目快速開始起始模板的實(shí)現(xiàn)

    本文主要介紹了springcloud項(xiàng)目快速開始起始模板思路的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Java中常見字符串拼接九種方式詳細(xì)例子

    Java中常見字符串拼接九種方式詳細(xì)例子

    這篇文章主要給大家介紹了關(guān)于Java中常見字符串拼接的九種方式,字符串拼接是我們在Java代碼中比較經(jīng)常要做的事情,就是把多個(gè)字符串拼接到一起,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07

最新評論