天天看點

SpringBoot 建立定時任務、異步調用SpringBoot 使用定時任務@Scheduled-fixedRate方式SpringBoot 使用定時任務@Scheduled-cron方式SpringBoot 使用@Async實作異步調用Spring Boot 使用@Async 實作異步調用-異步回調結果Spring Boot 使用@Async 實作異步調用-自定義線程池通過資料庫簡單的配置定時任務

SpringBoot 建立定時任務、異步調用

  • SpringBoot 使用定時任務@Scheduled-fixedRate方式
    • 建立定時任務
    • `@Scheduled`參數說明
  • SpringBoot 使用定時任務@Scheduled-cron方式
    • 修改 SchedulingTask(定時任務實作類)
    • 參數說明
      • cron 常用表達式例子
  • SpringBoot 使用@Async實作異步調用
    • 同步調用
    • 異步調用
  • Spring Boot 使用@Async 實作異步調用-異步回調結果
  • Spring Boot 使用@Async 實作異步調用-自定義線程池
  • 通過資料庫簡單的配置定時任務

SpringBoot 使用定時任務@Scheduled-fixedRate方式

在項目開發中,經常需要定時任務來幫助我們來做一些内容,比如定時發送短息/站内信、資料彙總統計、業務監控等。

建立定時任務

spring boot

中填寫定時任務是非常簡單的事,下面通過執行個體介紹如何在

spring boot

中建立定時任務

  • pom 配置

    (隻需要引入

    spring-boot-starter

    jar包即可,

    spring-boot-starter

    中已經内置了定時的方法)
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
           
  • spring boot

    主類中加入

    @EnableScheduling

    注解,啟用定時任務配置
package com.djy.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class SpringDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringDemoApplication.class, args);
    }

}
           
  • 建立

    定時任務實作類

@Component
public class SchedulingTask {
    private static final SimpleDateFormat f = new SimpleDateFormat("HH:mm:ss");

    //五秒執行一次
    @Scheduled(fixedRate = 5000)
    public void processFixedRate() {
        System.out.println("Scheduled-fixedRate 方式:開始定時任務,現在時間:"+f.format(new Date()));
    }
}
           

運作程式,控制台中可以看到類似的輸出,定時任務開始正常運作了。

SpringBoot 建立定時任務、異步調用SpringBoot 使用定時任務@Scheduled-fixedRate方式SpringBoot 使用定時任務@Scheduled-cron方式SpringBoot 使用@Async實作異步調用Spring Boot 使用@Async 實作異步調用-異步回調結果Spring Boot 使用@Async 實作異步調用-自定義線程池通過資料庫簡單的配置定時任務

@Scheduled

參數說明

在上面的例子中,使用了

@Scheduled(fixedRate = 5000)

注解來定義每5秒執行的任務,對于

@Scheduled

的使用可以總結如下幾種方式:

fixedRate 說明

  • @Scheduled(fixedRate = 5000)

    上一次開始執行時間點之後5秒在執行
  • @Scheduled(fixedDelay = 5000)

    上一次執行完畢時間點之後5秒執行
  • @Scheduled(initialDelay = 1000 ,fixedRate = 5000)

    第一次延遲1秒後執行,之後按fixedRate的規則每5秒執行一次

SpringBoot 使用定時任務@Scheduled-cron方式

修改 SchedulingTask(定時任務實作類)

@Component
public class SchedulingTask {
    private static final SimpleDateFormat f = new SimpleDateFormat("HH:mm:ss");


//    @Scheduled(fixedRate = 5000)
//    public void processFixedRate() {
//        System.out.println("Scheduled-fixedRate 方式:開始定時任務,現在時間:"+f.format(new Date()));
//    }

    @Scheduled(cron = "*/5 * * * * ?")
    public void processCron() {
        System.out.println("Scheduled-cron 方式:開始定時任務,現在時間:"+f.format(new Date()));
    }
}
           

運作程式,控制台中可以看到類似的輸出,定時任務開始正常運作了。

SpringBoot 建立定時任務、異步調用SpringBoot 使用定時任務@Scheduled-fixedRate方式SpringBoot 使用定時任務@Scheduled-cron方式SpringBoot 使用@Async實作異步調用Spring Boot 使用@Async 實作異步調用-異步回調結果Spring Boot 使用@Async 實作異步調用-自定義線程池通過資料庫簡單的配置定時任務

參數說明

每一個域都使用數字,但還可以出現如下特殊字元,它們的含義是:

(1):表示比對該域的任意值。假如在Minutes域使用, 即表示每分鐘都會觸發事件。

(2)?:隻能用在DayofMonth和DayofWeek兩個域。它也比對域的任意值,但實際不會。因為DayofMonth和DayofWeek會互相影響。例如想在每月的20日觸發排程,不管20日到底是星期幾,則隻能使用如下寫法: 13 13 15 20 * ?, 其中最後一位隻能用?,而不能使用*,如果使用*表示不管星期幾都會觸發,實際上并不是這樣。

(3)-:表示範圍。例如在Minutes域使用5-20,表示從5分到20分鐘每分鐘觸發一次

(4)/:表示起始時間開始觸發,然後每隔固定時間觸發一次。例如在Minutes域使用5/20,則意味着5分鐘觸發一次,而25,45等分别觸發一次.

(5),:表示列出枚舉值。例如:在Minutes域使用5,20,則意味着在5和20分每分鐘觸發一次。

(6)L:表示最後,隻能出現在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最後的一個星期四觸發。

(7)W:表示有效工作日(周一到周五),隻能出現在DayofMonth域,系統将在離指定日期的最近的有效工作日觸發事件。例如:在 DayofMonth使用5W,如果5日是星期六,則将在最近的工作日:星期五,即4日觸發。如果5日是星期天,則在6日(周一)觸發;如果5日在星期一到星期五中的一天,則就在5日觸發。另外一點,W的最近尋找不會跨過月份 。

(8)LW:這兩個字元可以連用,表示在某個月最後一個工作日,即最後一個星期五。

(9)#:用于确定每個月第幾個星期幾,隻能出現在DayofMonth域。例如在4#2,表示某月的第二個星期三。

cron 常用表達式例子

(0)0/20 * * * * ? 表示每20秒 調整任務

(1)0 0 2 1 * ? 表示在每月的1日的淩晨2點調整任務

(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15執行作業

(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每個月的最後一個星期五上午10:15執行作

(4)0 0 10,14,16 * * ? 每天上午10點,下午2點,4點

(5)0 0/30 9-17 * * ? 朝九晚五工作時間内每半小時

(6)0 0 12 ? * WED 表示每個星期三中午12點

(7)0 0 12 * * ? 每天中午12點觸發

(8)0 15 10 ? * * 每天上午10:15觸發

(9)0 15 10 * * ? 每天上午10:15觸發

(10)0 15 10 * * ? * 每天上午10:15觸發

(11)0 15 10 * * ? 2005 2005年的每天上午10:15觸發

(12)0 * 14 * * ? 在每天下午2點到下午2:59期間的每1分鐘觸發

(13)0 0/5 14 * * ? 在每天下午2點到下午2:55期間的每5分鐘觸發

(14)0 0/5 14,18 * * ? 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發

(15)0 0-5 14 * * ? 在每天下午2點到下午2:05期間的每1分鐘觸發

(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44觸發

(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15觸發

(18)0 15 10 15 * ? 每月15日上午10:15觸發

(19)0 15 10 L * ? 每月最後一日的上午10:15觸發

(20)0 15 10 ? * 6L 每月的最後一個星期五上午10:15觸發

(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最後一個星期五上午10:15觸發

(22)0 15 10 ? * 6#3 每月的第三個星期五上午10:15觸發

SpringBoot 使用@Async實作異步調用

什麼是

異步調用

?

異步調用

的應得是

同步調用

,同步調用指程式按照定義順序依次執行,每一行程式都必須等待上一行下執行程式執行完成之後才執行,

異步調用

指程式在順序執行時,不等待

異步調用

的語句傳回結果裡就執行後。

同步調用

下面通過一個簡單的示例來直覺的了解什麼是同步調用:

  • 定義Task類,建立三個處理函數分别模拟三個執行任務的操作,操作消耗時間随機取(10秒内)
@Component
public class MyTask {

    public static Random random = new Random();

    public void doTaskOne() throws Exception {
        System.out.println("開始做任務一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
    }

    public void doTaskTwo() throws Exception {
        System.out.println("開始做任務二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
    }

    public void doTaskThree() throws Exception {
        System.out.println("開始做任務三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
    }
}
           
  • 在單元測試用例中,注入Task對象,并在測試用例中執行

    doTaskOne

    doTaskTwo

    doTaskThree

    三個函數。
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAsyncApplicationTests {
    @Test
    public void contextLoads() {
   }
    @Autowired
    private MyTask myTask;
    @Test
    public void testTask() throws Exception{
        myTask.doTaskOne();
        myTask.doTaskTwo();
        myTask.doTaskThree();
   }
}
           
  • 執行單元測試,可以看到類似如下輸出:
開始做任務一
完成任務一,耗時:8653毫秒
開始做任務二
完成任務二,耗時:5215毫秒
開始做任務三
完成任務三,耗時:648毫秒
           

任務一、任務二、任務三順序的執行完了,換言之 doTaskOne 、 doTaskTwo 、 doTaskThree 三個函數順序的執行完成。

異步調用

上述的同步調用雖然順利的執行完了三個任務,但是可以看到執行時間比較長,若這三個任務本身之間不存在依賴關系,可以并發執行的話,同步調用在執行效率方面就比較差,可以考慮通過異步調用的方式來并發執行。

在Spring Boot中,我們隻需要通過使用

@Async

注解就能簡單的将原來的同步函數變為異步函數,Task類改在為如下模式:

@Component
public class MyTask {
    public static Random random =new Random();
    @Async
    public void doTaskOne() throws Exception {
        System.out.println("開始做任務一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
   }
    @Async
    public void doTaskTwo() throws Exception {
        System.out.println("開始做任務二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
   }
    @Async
    public void doTaskThree() throws Exception {
        System.out.println("開始做任務三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
   }
}
           

為了讓

@Async

注解能夠生效,還需要在Spring Boot的主程式中配置

@EnableAsync

,如下所示:

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

此時可以反複執行單元測試,您可能會遇到各種不同的結果,比如:

  • 沒有任何任務相關的輸出
  • 有部分任務相關的輸出
  • 亂序的任務相關的輸出

原因是目前 doTaskOne 、 doTaskTwo 、 doTaskThree 三個函數的時候已經是異步執行了。主程式在異步調用之後,主程式并不會理

會這三個函數是否執行完成了,由于沒有其他需要執行的内容,是以程式就自動結束了,導緻了不完整或是沒有輸出任務相關内容的

情況。

注: @Async所修飾的函數不要定義為static類型,這樣異步調用不會生效

Spring Boot 使用@Async 實作異步調用-異步回調結果

為了讓 doTaskOne 、 doTaskTwo 、 doTaskThree 能正常結束,假設我們需要統計一下三個任務并發執行共耗時多少,這就需要等到

上述三個函數都完成調動之後記錄時間,并計算結果。

那麼我們如何判斷上述三個異步調用是否已經執行完成呢?我們需要使用 Future 來傳回異步調用的結果,改造完成後如下:

@Component
public class MyTask {
    public static Random random =new Random();
    @Async
    public Future<String> doTaskOne() throws Exception {
        System.out.println("開始做任務一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
        return new AsyncResult<>("完成任務一");
   }
    @Async
    public Future<String> doTaskTwo() throws Exception {
        System.out.println("開始做任務二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
        return new AsyncResult<>("完成任務二");
   }
    @Async
    public Future<String> doTaskThree() throws Exception {
        System.out.println("開始做任務三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
        return new AsyncResult<>("完成任務三");
   }
}
           

下面我們改造一下測試用例,讓測試在等待完成三個異步調用之後來做一些其他事情。

@Test
    public void testTask() throws Exception{
//       myTask.doTaskOne();
//       myTask.doTaskTwo();
//       myTask.doTaskThree();
        long start = System.currentTimeMillis();
        Future<String> task1 = myTask.doTaskOne();
        Future<String> task2 = myTask.doTaskTwo();
        Future<String> task3 = myTask.doTaskThree();
        while(true) {
            if(task1.isDone() && task2.isDone() && task3.isDone()) {
                // 三個任務都調用完成,退出循環等待
                break;
           }
            Thread.sleep(1000);
       }
            long end = System.currentTimeMillis();
        System.out.println("任務全部完成,總耗時:" + (end - start) + "毫秒");
   }
           

看看我們做了哪些改變:

  • 在測試用例一開始記錄開始時間
  • 在調用三個異步函數的時候,傳回 Future 類型的結果對象
  • 在調用完三個異步函數之後,開啟一個循環,根據傳回的 Future 對象來判斷三個異步函數是否都結束了。若都結

    束,就結束循環;若沒有都結束,就等1秒後再判斷。

  • 跳出循環之後,根據結束時間 - 開始時間,計算出三個任務并發執行的總耗時。

執行一下上述的單元測試,可以看到如下結果

開始做任務二
開始做任務一
開始做任務三
完成任務二,耗時:1904毫秒
完成任務三,耗時:1914毫秒
完成任務一,耗時:4246毫秒
任務全部完成,總耗時:5008毫秒
           

Spring Boot 使用@Async 實作異步調用-自定義線程池

開啟異步注解

@EnableAsync

方法上加

@Async

預設實作

SimpleAsyncTaskExecutor

不是真的線程池,這個類不重用線程,每次調用

都會建立一個新的線程

  • 配置線程池
@Bean("myTaskExecutor")
    public Executor myTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);//核心線程數量,線程池建立時候初始化的線程數
        executor.setMaxPoolSize(15);//最大線程數,隻有在緩沖隊列滿了之後才會申請超過核心線程數的線程
        executor.setQueueCapacity(200);//緩沖隊列,用來緩沖執行任務的隊列
        executor.setKeepAliveSeconds(60);//當超過了核心線程數之外的線程在空閑時間到達之後會被銷毀
        executor.setThreadNamePrefix("myTask-");//設定好了之後可以友善我們定位處理任務所在的線程池
        executor.setWaitForTasksToCompleteOnShutdown(true);//用來設定線程池關閉的時候等待所有任務都完成再繼續銷毀其他的Bean
        executor.setAwaitTerminationSeconds(60);//該方法用來設定線程池中任務的等待時間,如果超過這個時候還沒有銷毀就強制銷毀,以確定應用最後能夠被關閉,而不是阻塞住。
        //線程池對拒絕任務的處理政策:這裡采用了CallerRunsPolicy政策,當線程池沒有處理能力的時候,該政策會直接在execute 方法的調用線程中運作被拒絕的任務;如果執行程式已關閉,則會丢棄該任務
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
   }
           
  • 改造MyTask
@Component
public class MyTask {
    public static Random random =new Random();
    @Async("myTaskExecutor")
    public Future<String> doTaskOne() throws Exception {
        System.out.println("開始做任務一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
        return new AsyncResult<>("完成任務一");
   }
    @Async("myTaskExecutor")
    public Future<String> doTaskTwo() throws Exception {
        System.out.println("開始做任務二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
        return new AsyncResult<>("完成任務二");
   }
    @Async("myTaskExecutor")
    public Future<String> doTaskThree() throws Exception {
        System.out.println("開始做任務三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
        return new AsyncResult<>("完成任務三");
   }
}
           

執行一下上述的單元測試,可以看到如下結果:

開始做任務二
開始做任務三
開始做任務一
完成任務一,耗時:1090毫秒
完成任務三,耗時:4808毫秒
完成任務二,耗時:5942毫秒
任務全部完成,總耗時:6018毫秒
           

通過資料庫簡單的配置定時任務

package com.example.scheduledTask;
 
import com.example.mybatis.dao.SysTaskMapper;
import com.example.mybatis.model.SysTask;
import com.example.mybatis.model.SysTaskExample;
import com.example.util.SpringUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
 
import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
 
@Lazy(value = false)
@Component
public class SysTaskConfig implements SchedulingConfigurer {
 
    protected static Logger logger = LoggerFactory.getLogger(SysTaskConfig.class);
 
    private SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
    @Resource
    private SysTaskMapper sysTaskMapper;
 
    //從資料庫裡取得所有要執行的定時任務
    private List<SysTask> getAllTasks() {
        SysTaskExample example=new SysTaskExample();
        example.createCriteria().andIsDeleteEqualTo((byte) 0);
        return sysTaskMapper.selectByExample(example);
    }
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        List<SysTask> tasks=getAllTasks();
        logger.info("定時任務啟動,預計啟動任務數量="+tasks.size()+"; time="+sdf.format(new Date()));
 
        //校驗資料(這個步驟主要是為了列印日志,可以省略)
        checkDataList(tasks);
 
        //通過校驗的資料執行定時任務
        int count = 0;
        if(tasks.size()>0) {
            for (int i = 0; i < tasks.size(); i++) {
                try {
                    taskRegistrar.addTriggerTask(getRunnable(tasks.get(i)), getTrigger(tasks.get(i)));
                    count++;
                } catch (Exception e) {
                    logger.error("定時任務啟動錯誤:" + tasks.get(i).getClassName() + ";" + tasks.get(i).getMethodName() + ";" + e.getMessage());
                }
            }
        }
        logger.info("定時任務實際啟動數量="+count+"; time="+sdf.format(new Date()));
    };
 
 
    private Runnable getRunnable(SysTask task){
        return new Runnable() {
            @Override
            public void run() {
                try {
                    Object obj = SpringUtil.getBean(task.getClassName());
                    Method method = obj.getClass().getMethod(task.getMethodName(),null);
                    method.invoke(obj);
                } catch (InvocationTargetException e) {
                    logger.error("定時任務啟動錯誤,反射異常:"+task.getClassName()+";"+task.getMethodName()+";"+ e.getMessage());
                } catch (Exception e) {
                    logger.error(e.getMessage());
                }
            }
        };
    }
 
    private Trigger getTrigger(SysTask task){
        return new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                //将Cron 0/1 * * * * ? 輸入取得下一次執行的時間
                CronTrigger trigger = new CronTrigger(task.getCron());
                Date nextExec = trigger.nextExecutionTime(triggerContext);
                return nextExec;
            }
        };
 
    }
 
    private List<SysTask> checkDataList(List<SysTask> list) {
        String errMsg="";
        for(int i=0;i<list.size();i++){
            if(!checkOneData(list.get(i)).equalsIgnoreCase("success")){
                errMsg+=list.get(i).getTaskName()+";";
                list.remove(list.get(i));
                i--;
            };
        }
        if(!StringUtils.isBlank(errMsg)){
            errMsg="未啟動的任務:"+errMsg;
            logger.error(errMsg);
        }
    return list;
    }
 
    private String checkOneData(SysTask task){
        String result="success";
        Class cal= null;
        try {
            cal = Class.forName(task.getClassName());
 
            Object obj =SpringUtil.getBean(cal);
            Method method = obj.getClass().getMethod(task.getMethodName(),null);
            String cron=task.getCron();
            if(StringUtils.isBlank(cron)){
                result="定時任務啟動錯誤,無cron:"+task.getTaskName();
                logger.error(result);
            }
        } catch (ClassNotFoundException e) {
            result="定時任務啟動錯誤,找不到類:"+task.getClassName()+ e.getMessage();
            logger.error(result);
        } catch (NoSuchMethodException e) {
            result="定時任務啟動錯誤,找不到方法,方法必須是public:"+task.getClassName()+";"+task.getMethodName()+";"+ e.getMessage();
            logger.error(result);
        } catch (Exception e) {
          logger.error(e.getMessage());
         }
        return result;
    }
 
 
}
           

資料庫配置

SpringBoot 建立定時任務、異步調用SpringBoot 使用定時任務@Scheduled-fixedRate方式SpringBoot 使用定時任務@Scheduled-cron方式SpringBoot 使用@Async實作異步調用Spring Boot 使用@Async 實作異步調用-異步回調結果Spring Boot 使用@Async 實作異步調用-自定義線程池通過資料庫簡單的配置定時任務

執行的方法

SpringBoot 建立定時任務、異步調用SpringBoot 使用定時任務@Scheduled-fixedRate方式SpringBoot 使用定時任務@Scheduled-cron方式SpringBoot 使用@Async實作異步調用Spring Boot 使用@Async 實作異步調用-異步回調結果Spring Boot 使用@Async 實作異步調用-自定義線程池通過資料庫簡單的配置定時任務