spring設置定時任務方式(@Scheduled)
以前用過這個注解實現(xiàn)定時任務,但是只是使用,現(xiàn)在做項目又用到了這個功能,系統(tǒng)的學習一下~
spring定時任務設置有兩種方式,注解和xml配置。
推薦使用注解,在本文章也主要介紹注解方式配置
一:注解方式配置定時任務
下面的步驟默認spring的其他配置項都已經配置好(比如啟動注解配置,包路徑掃描等)
1:在spring配置文件中配置,添加命名空間
xmlns添加:
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation添加 注意"4.3"這是版本號,要修改和你的其他xsd版本號一致
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd"
啟動注解驅動 注意“dataScheduler”為自定義名稱,可以通過自己的業(yè)務定義 合適 的名稱
<task:annotation-driven scheduler="dataScheduler"/>
開啟任務調度器,并配置線程池大小
- 注意此處的id指定的就是上面的自定義名稱
- spring的任務調度默認是單線程的,如果你的項目會有多任務定時執(zhí)行,并且執(zhí)行時間會相交的話,應該根據(jù)任務的具體執(zhí)行情況配置線程池大小
- 如果不配置線程池,并且A和B任務在同一時間執(zhí)行,A先執(zhí)行的話,B要等待A執(zhí)行完才可以執(zhí)行,AB不會同時執(zhí)行
<task:scheduler id="dataScheduler" pool-size="5"/>
2:使用注解配置定時任務
在你需要配置定時任務的方法上使用注解@Scheduled即可,下面一個簡單案例:
- 注意 下面的案例是在每天的早上2點執(zhí)行
- “0 0 2 * * *”是怎么組合的?下面會詳細介紹@Scheduled()注解
@Scheduled(cron = "0 0 2 * * *") public void init(){ todo... }
在此需要注意:@Scheduled只能注釋在無參的方法上,我看網上有許多博客說必須無參無返回值的,但是經過我的測試有返回值是可以的,可能是版本更新了吧。
現(xiàn)在就算是完成spring定時器的使用了,下面讓我們來詳細的看一下@Scheduled注解吧~
二:@Scheduled
@Scheduled注解是Spring專門為定時任務設計的注解
首先,讓我們來看看這個注解是怎么組成的吧(適用于版本JDK8與spring4.3及其以上)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Schedules.class) public @interface Scheduled { String cron() default ""; String zone() default ""; long fixedDelay() default -1L; String fixedDelayString() default ""; long fixedRate() default -1L; String fixedRateString() default ""; long initialDelay() default -1L; String initialDelayString() default ""; }
從上述代碼中看以看出:
1:@Scheduled被注解部分:
- 元注解@Target表明@Scheduled注解可以在方法上使用(ElementType.METHOD),也可以作為元注解對其他注解進行注解(ElementType.ANNOTATION_TYPE)
- 元注解@Retention表明此注解會被JVM所保留,也就是會保存在運行時(RetentionPolicy.RUNTIME)
- 元注解@Documented表明此注解應該被 javadoc工具記錄。默認情況下javadoc是不包括注解的。
- JDK8添加的注解@Repeatable表明此注解可以在同一個地方被重復使用
上述的所涉及到的注解有不清楚作用的,可以自行baidu\google,網上有好多介紹的文章。
2:@Scheduled參數(shù)部分,總共包含8各部分,我們來分別看一下其作用:
- cron:一個類似cron的表達式,擴展了通常的UN * X定義,包括秒,分,時,星期,月,年的觸發(fā)器。
- fixedDelay:在最后一次調用結束和下一次調用開始之間以固定周期(以毫秒為單位)執(zhí)行帶注釋的方法。(要等待上次任務完成后)
- fixedDelayString:同上面作用一樣,只是String類型
- fixedRate:在調用之間以固定的周期(以毫秒為單位)執(zhí)行帶注釋的方法。(不需要等待上次任務完成)
- fixedRateString:同上面作用一樣,只是String類型
- initialDelay:第一次執(zhí)行fixedRate()或fixedDelay()任務之前延遲的毫秒數(shù) 。
- initialDelayString:同上面作用一樣,只是String類型
- zone:指明解析cron表達式的時區(qū)。
cron可以組合出更多的定時情況,fixedDelay和fixedRate只能定義每隔多長時間執(zhí)行一次。
在上述cron、fixedDelay、fixedRate 只能同時存在一個,使用其中一個就不能使用另外的一個,否則會報錯“java.lang.IllegalStateException”
3:cron參數(shù)
一個cron表達式可以有6個元素或者7個元素組成(“年”這個元素可以省略,省略之后就是默認“每一年”)
3.1:按順序依次為:
- 秒(0~59)
- 分鐘(0~59)
- 小時(0~23)
- 天(0~31)
- 月(0~11)
- 星期(1~7 )或者( SUN,MON,TUE,WED,THU,F(xiàn)RI,SAT。其中SUN = 1)
- 年份(1970-2099)
3.2:每個元素可以接受的值:
字段 | 允許值 | 允許的特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小時 | 0-23 | , - * / |
日期 | 1-31 | , - * ? / L W C |
月份 | 1-12 或者 JAN-DEC | , - * / |
星期 | 1-7 或者 SUN-SAT | , - * ? / L C # |
年 | 空, 1970-2099 | , - * / |
3.3:一些特殊字符解釋與注意事項,可以結合下面的小案例來理解:
其中每個元素可以是一個值(如6),一個連續(xù)區(qū)間(9-12),一個間隔時間(8-18/4)(/表示每隔4小時),一個列表(1,3,5),通配符。
其中的“日”由于"月份中的日期"和"星期"這兩個元素互斥的,必須要對其中一個設置“?”。
有些子表達式能包含一些范圍或列表
- 例如:子表達式(天(星期))可以為 “MON-FRI”,“MON,WED,F(xiàn)RI”,“MON-WED,SAT”
“*”字符代表所有可能的值
“/”字符用來指定數(shù)值的增量
- 例如:在子表達式(分鐘)里的“0/15”表示從第0分鐘開始,每15分鐘
- 在子表達式(分鐘)里的“3/20”表示從第3分鐘開始,每20分鐘(它和“3,23,43”)的含義一樣
“?”字符僅被用于天(月)和天(星期)兩個子表達式,表示不指定值
- 當2個子表達式其中之一被指定了值以后,為了避免沖突,需要將另一個子表達式的值設為“?”
“L” 字符僅被用于天(月)和天(星期)兩個子表達式,它是單詞“last”的縮寫
- 如果在“L”前有具體的內容,它就具有其他的含義了。例如:“6L”表示這個月的倒數(shù)第6天
- 注意:在使用“L”參數(shù)時,不要指定列表或范圍,因為這會導致問題
“W” 字符代表著平日(Mon-Fri),并且僅能用于日域中。它用來指定離指定日的最近的一個平日。大部分的商業(yè)處理都是基于工作周的,所以 W 字符可能是非常重要的。
- 例如,日域中的 15W 意味著 “離該月15號的最近一個平日。” 假如15號是星期六,那么 trigger 會在14號(星期五)觸發(fā),因為星期四比星期一離15號更近。
“C”:代表“Calendar”的意思。它的意思是計劃所關聯(lián)的日期,如果日期沒有被關聯(lián),則相當于日歷中所有日期。例如5C在日期字段中就相當于日歷5日以后的第一天。1C在星期字段中相當于星期日后的第一天。
3.4:一些小案例:
- “0 0 10,14,16 * * ?” 每天上午10點,下午2點,4點
- “0 0/30 9-17 * * ?” 朝九晚五工作時間內每半小時
- “0 0 12 ? * WED” 表示每個星期三中午12點
- “0 0 12 * * ?” 每天中午12點觸發(fā)
- “0 15 10 ? * *” 每天上午10:15觸發(fā)(這個和下一個案例說明,必須"月份中的日期"和"星期"中有一個設置為“?”)
- “0 15 10 * * ?” 每天上午10:15觸發(fā)
- “0 15 10 * * ? *” 每天上午10:15觸發(fā)(7個元素類型案例,第七個元素代表年)
- “0 15 10 * * ? 2005” 2005年的每天上午10:15觸發(fā)
- “0 * 14 * * ?” 在每天下午2點到下午2:59期間的每1分鐘觸發(fā)
- “0 0/5 14 * * ?” 在每天下午2點到下午2:55期間的每5分鐘觸發(fā)
- “0 0/5 14,18 * * ?” 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發(fā)
- “0 0-5 14 * * ?” 在每天下午2點到下午2:05期間的每1分鐘觸發(fā)
- “0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44觸發(fā)
- “0 15 10 ? * MON-FRI” 周一至周五的上午10:15觸發(fā)
- “0 15 10 15 * ?” 每月15日上午10:15觸發(fā)
- “0 15 10 L * ?” 每月最后一日的上午10:15觸發(fā)
- “0 15 10 ? * 6L” 每月的最后一個星期五上午10:15觸發(fā)
- “0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一個星期五上午10:15觸發(fā)
- “0 15 10 ? * 6#3” 每月的第三個星期五上午10:15觸發(fā)
到這個地方你應該對@Scheduled有一個較全面的理解了,下面我們就來簡單的看一下其實現(xiàn)原理吧~
三:原理簡介
1:主要過程:
spring在使用applicationContext將類全部初始化。
調用ScheduledAnnotationBeanPostProcessor類中的postProcessAfterInitialization方法獲取項目中所有被注解 @Scheduled注解的方法 。
通過processScheduled方法將所有定時的方法存放在Set tasks = new LinkedHashSet(4); 定時任務隊列中,并解析相應的參數(shù)。順序存放,任務也是順序執(zhí)行。存放順序為cron>fixedDelay>fixedRate
將解析參數(shù)后的定時任務存放在一個初始容量為16 的map中,key為bean name,value為定時任務:private final Map<Object, Set> scheduledTasks = new IdentityHashMap(16);
之后交給ScheduledTaskRegistrar類的方法scheduleTasks去添加定時任務。
2:上述就是一個大致過程,下面看一下相應的源碼:
注意 :spring對定時任務的操作的源碼全部在spring-context.jar包下的org.springframework.scheduling包下面,主要包含三部分:annotation、config、 support,大家有興趣的話可以去看看
1:獲取項目中所有被注解 @Scheduled注解的方法
public Object postProcessAfterInitialization(Object bean, String beanName) { Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); if (!this.nonAnnotatedClasses.contains(targetClass)) { Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, new MetadataLookup<Set<Scheduled>>() { public Set<Scheduled> inspect(Method method) { //獲取注解方法 **Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);** return !scheduledMethods.isEmpty() ? scheduledMethods : null; } }); if (annotatedMethods.isEmpty()) { ... } else { Iterator var5 = annotatedMethods.entrySet().iterator(); while(var5.hasNext()) { Entry<Method, Set<Scheduled>> entry = (Entry)var5.next(); Method method = (Method)entry.getKey(); Iterator var8 = ((Set)entry.getValue()).iterator(); while(var8.hasNext()) { Scheduled scheduled = (Scheduled)var8.next(); //將獲取的任務進行參數(shù)解析并存放到任務隊列 this.processScheduled(scheduled, method, bean); } } ... } } return bean; }
2:通過processScheduled方法將所有定時的方法存放在定時任務隊列中
protected void processScheduled(Scheduled scheduled, Method method, Object bean) { try { ... //解析initialDelayString參數(shù) String initialDelayString = scheduled.initialDelayString(); if (StringUtils.hasText(initialDelayString)) { ... } //解析cron參數(shù) String cron = scheduled.cron(); if (StringUtils.hasText(cron)) { ... //存放到任務隊列中 tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)))); } ... //解析fixedDelay參數(shù) long fixedDelay = scheduled.fixedDelay(); if (fixedDelay >= 0L) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay))); } String fixedDelayString = scheduled.fixedDelayString(); if (StringUtils.hasText(fixedDelayString)) { ... //存放到任務隊列中 tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay))); } //解析fixedRate參數(shù) long fixedRate = scheduled.fixedRate(); if (fixedRate >= 0L) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay))); } String fixedRateString = scheduled.fixedRateString(); if (StringUtils.hasText(fixedRateString)) { ... //存放到任務隊列中 tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay))); } Assert.isTrue(processedSchedule, errorMessage); Map var19 = this.scheduledTasks; //并發(fā)控制并將任務存放在map中 synchronized(this.scheduledTasks) { Set<ScheduledTask> registeredTasks = (Set)this.scheduledTasks.get(bean); if (registeredTasks == null) { registeredTasks = new LinkedHashSet(4); //將任務存放在map中 this.scheduledTasks.put(bean, registeredTasks); } ((Set)registeredTasks).addAll(tasks); } } catch (IllegalArgumentException var26) { throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " + var26.getMessage()); } }
3:之后交給ScheduledTaskRegistrar類的方法scheduleTasks去添加定時任務
protected void scheduleTasks() { if (this.taskScheduler == null) { this.localExecutor = Executors.newSingleThreadScheduledExecutor(); this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); } Iterator var1; if (this.triggerTasks != null) { var1 = this.triggerTasks.iterator(); while(var1.hasNext()) { TriggerTask task = (TriggerTask)var1.next(); this.addScheduledTask(this.scheduleTriggerTask(task)); } } if (this.cronTasks != null) { var1 = this.cronTasks.iterator(); while(var1.hasNext()) { CronTask task = (CronTask)var1.next(); this.addScheduledTask(this.scheduleCronTask(task)); } } IntervalTask task; if (this.fixedRateTasks != null) { var1 = this.fixedRateTasks.iterator(); while(var1.hasNext()) { task = (IntervalTask)var1.next(); this.addScheduledTask(this.scheduleFixedRateTask(task)); } } if (this.fixedDelayTasks != null) { var1 = this.fixedDelayTasks.iterator(); while(var1.hasNext()) { task = (IntervalTask)var1.next(); this.addScheduledTask(this.scheduleFixedDelayTask(task)); } } }
此部分只是對原理進行了簡單的介紹,如果有興趣深入了解,可以去看看源碼~
四:其他
做定時任務還可以使用java自帶的原生API,Timer和TimerTask去設計。
- Timer:一種工具,線程用其安排以后在后臺線程中執(zhí)行的任務??砂才湃蝿請?zhí)行一次,或者定期重復執(zhí)行。
- TimerTask:定義一個被執(zhí)行的任務,Timer 安排該任務為一次執(zhí)行或重復執(zhí)行的任務。
可以這樣理解Timer是一種定時器工具,用來在一個后臺線程計劃執(zhí)行指定任務,而TimerTask一個抽象類,它的子類代表一個可以被Timer計劃的任務。
這里就簡單的提一下,并不是本文的重點,具體的用法自行google吧~
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
springboot2版本無法加載靜態(tài)資源問題解決
這篇文章主要介紹了springboot2版本無法加載靜態(tài)資源問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11springboot打包無法讀取yml、properties等配置文件的解決
這篇文章主要介紹了springboot打包無法讀取yml、properties等配置文件的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04springboot中配置好登錄攔截后,swagger訪問不了問題
這篇文章主要介紹了springboot中配置好登錄攔截后,swagger訪問不了問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12SpringBoot整合MyBatis實現(xiàn)樂觀鎖和悲觀鎖的示例
這篇文章主要介紹了SpringBoot整合MyBatis實現(xiàn)樂觀鎖和悲觀鎖的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09SpringBoot打印系統(tǒng)執(zhí)行的sql語句及日志配置指南
這篇文章主要給大家介紹了關于SpringBoot打印系統(tǒng)執(zhí)行的sql語句及日志配置的相關資料,在Java SpringBoot項目中如果使用了Mybatis框架,默認情況下執(zhí)行的所有SQL操作都不會打印日志,需要的朋友可以參考下2023-10-10