Spring實現(xiàn)定時任務的幾種方式總結(jié)
一.簡介
Spring Task 是 Spring 框架提供的一種任務調(diào)度和異步處理的解決方案??梢园凑占s定的時間自動執(zhí)行某個代碼邏輯它可以幫助開發(fā)者在 Spring 應用中輕松地實現(xiàn)定時任務、異步任務等功能,提高應用的效率和可維護性。
二.實現(xiàn)
一.基于注解@Scheduled
1.通過@Scheduled注釋結(jié)合cron表達式實現(xiàn) cron表達式:
cron表達式是一種用于設(shè)置定時任務的語法規(guī)則。它由6個字段組成,分別表示秒、分、小 時、日期、月份和星期幾。每個字段都可以設(shè)置一個數(shù)字、一組數(shù)字(用逗號分隔)、一段數(shù)字范圍(用短橫線分隔)、通配符(表示任意值)或者特定的字符(如星期幾的英文縮寫)
語法規(guī)則:
示例:
0 0 0 * * ?:每天的零點整執(zhí)行任務。 0 0 */2 * * ?:每隔2小時執(zhí)行一次任務。 0 0 12 * * ?:每天中午12點執(zhí)行任務。 0 15 10 * * ?:每天上午10點15分執(zhí)行任務。 0 0 6,18 * * ?:每天的早上6點和晚上6點執(zhí)行任務。 0 0/30 8-18 * * ?:每天的上午8點到下午6點之間,每隔30分鐘執(zhí)行一次任務。 0 0 0 1 1 ?:每年的1月1日零點整執(zhí)行任務。 0 0 0 * * 2:每周的星期二零點整執(zhí)行任務。 0 0 0 ? * 6#3:每月的第三個星期六零點整執(zhí)行任務。 0 0 0 L * ?:每個月的最后一天零點整執(zhí)行任務。 ————————————————
結(jié)合@Scheduled:
@Scheduled(cron ="*/6 * * * * ?") public void sayHello() { System.out.println("hello"); }
輸出結(jié)果:
注:啟動類需要能掃描到定時任務類,否則定時任務啟動不起來。
除了cron表達式外,還支持(感興趣可以進一步了解)
1.fixedRate:控制方法執(zhí)行的間隔時間,是以上一次方法執(zhí)行完開始算起,如上一次方法執(zhí)行阻塞住了,那么直到上一次執(zhí)行完,并間隔給定的時間后,執(zhí)行下一次。
2.initialDelay:initialDelay = 10000 表示在容器啟動后,延遲10秒后再執(zhí)行一次定時器。
優(yōu)缺點:
優(yōu):添加注解即可,使用方便。
缺:1.@Scheduled作用在方法上,方法不能有參數(shù)
2.@Scheduled注解只能在開始就寫好,無法動態(tài)定義
3.spring支持的springtask的cron語句無法識別年份,也就是定時任務以固定頻率執(zhí)行,無法做到只執(zhí)行一次。
二.基于接口方式SchedulingConfigurer:
為了實現(xiàn)動態(tài)定義定時任務
一.創(chuàng)建數(shù)據(jù)庫表和相應字段存放cron語句
drop table if exists scheduled; create table scheduled ( cron_id varchar(30) NOT NULL primary key, cron_name varchar(30) NULL, cron varchar(30) NOT NULL ); insert into scheduled values ('1','定時器任務一','0/6 * * * * ?');
二.新增mapper類獲取數(shù)據(jù)庫存放的cron表達式
@Select("select cron from cron_demo where cron_id=#{id}") public String getCronById(int id);
三.新建task類執(zhí)行定時任務
public class TaskDemo implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask();//or: addCronTask... } private void process(){ System.out.println("cron執(zhí)行"); }//要執(zhí)行的邏輯 }
注意實現(xiàn)SchedulingConfigurer接口
用于添加定時任務的方法在這里很多很靈活,如addTriggerTask,addCronTask,并且方法重載也較多,建議查看源碼學習
這里介紹常用api:addTriggerTask,和addCronTask
addTriggerTask:
第一個方法實際是調(diào)用第二個方法
Runable task為要執(zhí)行的邏輯(想要定時實現(xiàn)的方法),Trigger trigger為使用某種方式封裝的cron語句,介紹一個簡單易懂好用的實現(xiàn)類---CronTrigger
expression為cron表達式,zonid為代表時區(qū)(不用管,會調(diào)用系統(tǒng)默認時區(qū)),默認使用第一個構(gòu)造方法即可
第二種:
CronTask是TriggerTask的子類,其成員可謂非常人性化,expression即為cron表達式,構(gòu)造方法再傳入runable執(zhí)行內(nèi)容即可
示例:
public class TaskDemo implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addCronTask(this::process,cronMapper.getCronByid(1))) }//要執(zhí)行的邏輯 private void process(){ System.out.println("cron執(zhí)行"); } }
從上示例看出,當用addTriggetTask時,如果用Crontask,用法和addCronTask差不多,這兩個的底層都是調(diào)用了一個叫add的方法
擴展:Runnable runnabke的寫法
注:Runable是線程的知識點,由于本人目前沒有學習java線程部分,無法講解其底層原理,只介紹在實現(xiàn)定時任務時的用法
Runable只是一個接口,內(nèi)部只有一個void的run方法
需要定義實現(xiàn)類,如下在納新大作業(yè)中的實現(xiàn):
private class TaskRunnable implements Runnable{ private final Cron cron; public TaskRunnable(Cron cron) { this.cron = cron; } @Override public void run() { //定義任務要做的事,即把visibility字段設(shè)為0表示可見, // 同時把時間設(shè)為設(shè)定的發(fā)送時間 // (如果設(shè)為當前時間由于定時任務管理器CronManageTask掃面時間間隔問題會導致實際執(zhí)行時間與預期發(fā)送時間不一致) mailboxService.lambdaUpdate() .set(Email::getVisibility,(short)0) .set(Email::getSendTime,cron.getExecuteTime()) .eq(Email::getId,cron.getEmailId()) .update(); } }
這里的cron為從外傳入的參數(shù),可以通過構(gòu)造方法將cron傳入對象中,這就解決了@Secheduled無法傳遞參數(shù)的問題,
示例:
@RequiredArgsConstructor public class TaskDemo implements SchedulingConfigurer { private final CronMapper cronMapper; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { Runnable task =this::process; CronTask cronTask = new CronTask(task,cronMapper.getCronById(1)); taskRegistrar.addTriggerTask(cronTask); } private void process(){ System.out.println("cron執(zhí)行"); }//要執(zhí)行的邏輯 }
還有一個在查找博客時看到的示例,方法基本上一樣只不過使用了lambda表達式,但是匿名內(nèi)部類我只了解一點點,還請大佬help:
@Autowired protected CronMapper cronMapper; @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { scheduledTaskRegistrar.addTriggerTask(() -> process(), triggerContext -> { String cron = cronMapper.getCron(1); if (cron.isEmpty()) { System.out.println("cron is null"); } return new CronTrigger(cron).nextExecutionTime(triggerContext); }); } private void process() { System.out.println("基于接口定時任務"); }
三.基于ThreadPoolTaskScheduler輕量級多線程定時任務框架
上述基于接口的方法解決了基于注解無法實現(xiàn)的動態(tài)定義cron表達式和方法傳入?yún)?shù)的問題,但示例無法實現(xiàn)根據(jù)傳入的年份指定在某一年特定日期執(zhí)行定時任務,下面介紹一種實現(xiàn)方式
一.簡介:
springboot中有一個bean,ThreadPoolTaskScheduler,可以很方便的對重復執(zhí)行的任務進行調(diào)度管理;相比于通過java自帶的周期性任務線程池
ScheduleThreadPoolExecutor,此bean對象支持根據(jù)cron表達式創(chuàng)建周期性任務。
當然,ThreadPoolTaskScheduler其實底層使用也是java自帶的線程池。
二.常用api介紹
ThreadPoolTaskScheduler 內(nèi)部方法非常豐富,本文實現(xiàn)的是一種corn表達式,周期執(zhí)行
- schedule(Runnable task, Trigger trigger) corn表達式,周期執(zhí)行
- schedule(Runnable task, Date startTime) 定時執(zhí)行
- scheduleAtFixedRate(Runnable task, Date startTime, long period) 定時周期間隔時間執(zhí)行。間隔時間單位 TimeUnit.MILLISECONDS
scheduleAtFixedRate(Runnable task, long period) 間隔時間執(zhí)行。單位毫秒
三.上實戰(zhàn)
1.新建實現(xiàn)類cron(隨便取的名)這里直接使用lambda注解
@Data @AllArgsConstructor @NoArgsConstructor @TableName("thread_cron") public class cron { private String title; private LocalDate startTime;//起始時間 private LocalDate deadTime;//結(jié)束時間 private LocalDateTime executeTime;//運行時間 }
解釋:startTime為任務啟動年份第一天,deadTime為任務啟動年份最后一天(指定年份執(zhí)行,也可以根據(jù)需求調(diào)整),executeTime為任務執(zhí)行時間
2.創(chuàng)建對應的service接口和實現(xiàn)類
public interface ThreadService extends IService<Cron> { void startCron(Cron cron);//啟動定時任務 void stopCron(Cron cron);//停止定時任務 void changeCron(Cron cron);//更新定時任務 }
實現(xiàn)類的具體邏輯:
1.每個任務有一個執(zhí)行期限,就是cron類中的startTime和deadTime,這里一般存儲年份信息,將任務限定在某年執(zhí)行,在啟動定時任務也就是調(diào)用startCron方法時,需要判斷當前時間是否在期限內(nèi)
2.同一任務可能被多次啟動,這顯然是多余的,因此需要將已經(jīng)啟動過的定時任務放入一個集合中,在調(diào)用startCron時檢查當前任務是否在集合中。執(zhí)行定時任務的方法是ThreadPoolTaskScheduler中的public ScheduledFuture schedule(Runnable task, Trigger trigger)這個方法,可以看到,方法參數(shù)在上面基于接口處講過,方法返回值ScheduledFuture包含執(zhí)行的任務的詳細信息,停止任務也需要調(diào)用其中的boolean cancel(boolean mayInterruptIfRunning)方法,因此,可以用此類型的集合來存放執(zhí)行中的定時任務
示例:
準備:
private final ThreadPoolTaskScheduler threadPoolTaskScheduler; private final Map<Integer, ScheduledFuture<?>> futureMap = new HashMap<>(); @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); }
startCron:
public void startCron(Cron cron) { //1.判斷cron是否被執(zhí)行過 if(futureMap.containsKey(cron.getId())){log.info("定時任務存在 id={}",cron.getId());return;} //2.判斷是否還沒過執(zhí)行時間 //在springTask中,cron表達式無法對年進行定時,故使用startTime和deadTime來限制定時任務要執(zhí)行的年份 if(LocalDate.now().isEqual(cron.getStartTime()) || LocalDate.now().isEqual(cron.getDeadTime()) || (LocalDate.now().isAfter(cron.getStartTime()) && LocalDate.now().isBefore(cron.getDeadTime()))){ //提取執(zhí)行時間 LocalDateTime executeTime = cron.getExecuteTime(); //組裝cron表達式 DateTimeFormatter cronFormatter = DateTimeFormatter.ofPattern("s m H d M"); String cronExp = cronFormatter.format(executeTime)+" ?"; //執(zhí)行scheduled任務 ScheduledFuture<?> future = threadPoolTaskScheduler.schedule(new TaskRunnable(cron), new CronTrigger(cronExp)); //將future傳入futureMap集合表示任務啟動,避免任務重復啟動 futureMap.put(cron.getId(),future); //輸出日志 log.info("任務啟動,id:{},executeTime:{}",cron.getId(),cron.getExecuteTime()); } }
stopCron:
public void stopCron(Cron cron) { ScheduledFuture<?> future = futureMap.get(cron.getId()); if (future != null) { future.cancel(true); futureMap.remove(cron.getId()); log.info("任務停止,id:{}",cron.getId()); } }
changeCron:
public void changeCron(Cron cron) { startCron(cron); stopCron(cron); }
TaskRunnable類:
private class TaskRunnable implements Runnable{ private final Cron cron; public TaskRunnable(Cron cron) { this.cron = cron; } @Override public void run() { //定義任務要做的事 System.out.println("定時任務執(zhí)行,id:"+cron.getId()); } }
3.創(chuàng)建cronTaskManager類
注:cronTaskManager類上加注解@Compoment
上述解決了基于注解的三個問題,但是還存在一個問題,定時任務制定后被啟用需要保持服務器或應用程序一直被啟動,如果關(guān)閉應用程序,定時任務也將失效,因此需要一個類來管理定時任務,基本思路是:在應用啟動時每隔一段時間掃描一邊數(shù)據(jù)庫存放的定時任務,將其啟動或停止。
public class cronTaskManager { @Lazy private final ThreadService threadService; //每半個小時掃描一次 @Scheduled(cron = "0 0/30 * * * ?") public void cronManage() { log.info("定時任務啟動"); List<Cron> list = threadService.list(); list.forEach(cron -> { if (LocalDate.now().isAfter(cron.getDeadTime())) { threadService.stopCron(cron); threadService.removeById(cron.getId()); log.info("任務過期刪除,id:{},executeTime:{}",cron.getId(),cron.getExecuteTime()); } else { log.info("嘗試啟動任務,id:{},executeTime:{}",cron.getId(),cron.getExecuteTime()); threadService.startCron(cron); } }); } }
啟動應用定時啟動ronManager方法掃描數(shù)據(jù)庫存在的定時任務,如果任務過期則刪除,否則嘗試啟動。
以上就是Spring實現(xiàn)定時任務的幾種方式總結(jié)的詳細內(nèi)容,更多關(guān)于Spring實現(xiàn)定時任務的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springmvc處理模型數(shù)據(jù)ModelAndView過程詳解
這篇文章主要介紹了springmvc處理模型數(shù)據(jù)ModelAndView過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01Springboot詳解整合SpringSecurity實現(xiàn)全過程
Spring Security基于Spring開發(fā),項目中如果使用Springboot作為基礎(chǔ),配合Spring Security做權(quán)限更加方便,而Shiro需要和Spring進行整合開發(fā)。因此作為spring全家桶中的Spring Security在java領(lǐng)域很常用2022-07-07Springboot?如何使用BindingResult校驗參數(shù)
這篇文章主要介紹了Springboot?如何使用BindingResult校驗參數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01Spring Mybatis Mapper模糊查詢的幾種方法
在Spring結(jié)合Mybatis進行開發(fā)時,實現(xiàn)模糊查詢是一個常見需求,在Mybatis中,LIKE查詢可以通過多種方式實現(xiàn),本文給大家介紹了Spring Mybatis Mapper模糊查詢的幾種方法,需要的朋友可以參考下2024-03-03Spring Boot Admin Server管理客戶端過程詳解
這篇文章主要介紹了Spring Boot Admin Server管理客戶端過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-03-03SpringBoot面試突擊之過濾器和攔截器區(qū)別詳解
過濾器(Filter)和攔截器(Interceptor)都是基于?AOP(Aspect?Oriented?Programming,面向切面編程)思想實現(xiàn)的,用來解決項目中某一類問題的兩種“工具”,但二者有著明顯的差距,接下來我們一起來看2022-10-10