spring設(shè)置定時(shí)任務(wù)方式(@Scheduled)
以前用過(guò)這個(gè)注解實(shí)現(xiàn)定時(shí)任務(wù),但是只是使用,現(xiàn)在做項(xiàng)目又用到了這個(gè)功能,系統(tǒng)的學(xué)習(xí)一下~
spring定時(shí)任務(wù)設(shè)置有兩種方式,注解和xml配置。
推薦使用注解,在本文章也主要介紹注解方式配置
一:注解方式配置定時(shí)任務(wù)
下面的步驟默認(rèn)spring的其他配置項(xiàng)都已經(jīng)配置好(比如啟動(dòng)注解配置,包路徑掃描等)
1:在spring配置文件中配置,添加命名空間
xmlns添加:
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation添加 注意"4.3"這是版本號(hào),要修改和你的其他xsd版本號(hào)一致
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd"
啟動(dòng)注解驅(qū)動(dòng) 注意“dataScheduler”為自定義名稱,可以通過(guò)自己的業(yè)務(wù)定義 合適 的名稱
<task:annotation-driven scheduler="dataScheduler"/>
開(kāi)啟任務(wù)調(diào)度器,并配置線程池大小
- 注意此處的id指定的就是上面的自定義名稱
- spring的任務(wù)調(diào)度默認(rèn)是單線程的,如果你的項(xiàng)目會(huì)有多任務(wù)定時(shí)執(zhí)行,并且執(zhí)行時(shí)間會(huì)相交的話,應(yīng)該根據(jù)任務(wù)的具體執(zhí)行情況配置線程池大小
- 如果不配置線程池,并且A和B任務(wù)在同一時(shí)間執(zhí)行,A先執(zhí)行的話,B要等待A執(zhí)行完才可以執(zhí)行,AB不會(huì)同時(shí)執(zhí)行
<task:scheduler id="dataScheduler" pool-size="5"/>
2:使用注解配置定時(shí)任務(wù)
在你需要配置定時(shí)任務(wù)的方法上使用注解@Scheduled即可,下面一個(gè)簡(jiǎn)單案例:
- 注意 下面的案例是在每天的早上2點(diǎn)執(zhí)行
- “0 0 2 * * *”是怎么組合的?下面會(huì)詳細(xì)介紹@Scheduled()注解
@Scheduled(cron = "0 0 2 * * *")
public void init(){
todo...
}
在此需要注意:@Scheduled只能注釋在無(wú)參的方法上,我看網(wǎng)上有許多博客說(shuō)必須無(wú)參無(wú)返回值的,但是經(jīng)過(guò)我的測(cè)試有返回值是可以的,可能是版本更新了吧。
現(xiàn)在就算是完成spring定時(shí)器的使用了,下面讓我們來(lái)詳細(xì)的看一下@Scheduled注解吧~
二:@Scheduled
@Scheduled注解是Spring專門(mén)為定時(shí)任務(wù)設(shè)計(jì)的注解
首先,讓我們來(lái)看看這個(gè)注解是怎么組成的吧(適用于版本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),也可以作為元注解對(duì)其他注解進(jìn)行注解(ElementType.ANNOTATION_TYPE)
- 元注解@Retention表明此注解會(huì)被JVM所保留,也就是會(huì)保存在運(yùn)行時(shí)(RetentionPolicy.RUNTIME)
- 元注解@Documented表明此注解應(yīng)該被 javadoc工具記錄。默認(rèn)情況下javadoc是不包括注解的。
- JDK8添加的注解@Repeatable表明此注解可以在同一個(gè)地方被重復(fù)使用
上述的所涉及到的注解有不清楚作用的,可以自行baidu\google,網(wǎng)上有好多介紹的文章。
2:@Scheduled參數(shù)部分,總共包含8各部分,我們來(lái)分別看一下其作用:
- cron:一個(gè)類似cron的表達(dá)式,擴(kuò)展了通常的UN * X定義,包括秒,分,時(shí),星期,月,年的觸發(fā)器。
- fixedDelay:在最后一次調(diào)用結(jié)束和下一次調(diào)用開(kāi)始之間以固定周期(以毫秒為單位)執(zhí)行帶注釋的方法。(要等待上次任務(wù)完成后)
- fixedDelayString:同上面作用一樣,只是String類型
- fixedRate:在調(diào)用之間以固定的周期(以毫秒為單位)執(zhí)行帶注釋的方法。(不需要等待上次任務(wù)完成)
- fixedRateString:同上面作用一樣,只是String類型
- initialDelay:第一次執(zhí)行fixedRate()或fixedDelay()任務(wù)之前延遲的毫秒數(shù) 。
- initialDelayString:同上面作用一樣,只是String類型
- zone:指明解析cron表達(dá)式的時(shí)區(qū)。
cron可以組合出更多的定時(shí)情況,fixedDelay和fixedRate只能定義每隔多長(zhǎng)時(shí)間執(zhí)行一次。
在上述cron、fixedDelay、fixedRate 只能同時(shí)存在一個(gè),使用其中一個(gè)就不能使用另外的一個(gè),否則會(huì)報(bào)錯(cuò)“java.lang.IllegalStateException”
3:cron參數(shù)
一個(gè)cron表達(dá)式可以有6個(gè)元素或者7個(gè)元素組成(“年”這個(gè)元素可以省略,省略之后就是默認(rèn)“每一年”)
3.1:按順序依次為:
- 秒(0~59)
- 分鐘(0~59)
- 小時(shí)(0~23)
- 天(0~31)
- 月(0~11)
- 星期(1~7 )或者( SUN,MON,TUE,WED,THU,F(xiàn)RI,SAT。其中SUN = 1)
- 年份(1970-2099)
3.2:每個(gè)元素可以接受的值:
| 字段 | 允許值 | 允許的特殊字符 |
|---|---|---|
| 秒 | 0-59 | , - * / |
| 分 | 0-59 | , - * / |
| 小時(shí) | 0-23 | , - * / |
| 日期 | 1-31 | , - * ? / L W C |
| 月份 | 1-12 或者 JAN-DEC | , - * / |
| 星期 | 1-7 或者 SUN-SAT | , - * ? / L C # |
| 年 | 空, 1970-2099 | , - * / |
3.3:一些特殊字符解釋與注意事項(xiàng),可以結(jié)合下面的小案例來(lái)理解:
其中每個(gè)元素可以是一個(gè)值(如6),一個(gè)連續(xù)區(qū)間(9-12),一個(gè)間隔時(shí)間(8-18/4)(/表示每隔4小時(shí)),一個(gè)列表(1,3,5),通配符。
其中的“日”由于"月份中的日期"和"星期"這兩個(gè)元素互斥的,必須要對(duì)其中一個(gè)設(shè)置“?”。
有些子表達(dá)式能包含一些范圍或列表
- 例如:子表達(dá)式(天(星期))可以為 “MON-FRI”,“MON,WED,F(xiàn)RI”,“MON-WED,SAT”
“*”字符代表所有可能的值
“/”字符用來(lái)指定數(shù)值的增量
- 例如:在子表達(dá)式(分鐘)里的“0/15”表示從第0分鐘開(kāi)始,每15分鐘
- 在子表達(dá)式(分鐘)里的“3/20”表示從第3分鐘開(kāi)始,每20分鐘(它和“3,23,43”)的含義一樣
“?”字符僅被用于天(月)和天(星期)兩個(gè)子表達(dá)式,表示不指定值
- 當(dāng)2個(gè)子表達(dá)式其中之一被指定了值以后,為了避免沖突,需要將另一個(gè)子表達(dá)式的值設(shè)為“?”
“L” 字符僅被用于天(月)和天(星期)兩個(gè)子表達(dá)式,它是單詞“last”的縮寫(xiě)
- 如果在“L”前有具體的內(nèi)容,它就具有其他的含義了。例如:“6L”表示這個(gè)月的倒數(shù)第6天
- 注意:在使用“L”參數(shù)時(shí),不要指定列表或范圍,因?yàn)檫@會(huì)導(dǎo)致問(wèn)題
“W” 字符代表著平日(Mon-Fri),并且僅能用于日域中。它用來(lái)指定離指定日的最近的一個(gè)平日。大部分的商業(yè)處理都是基于工作周的,所以 W 字符可能是非常重要的。
- 例如,日域中的 15W 意味著 “離該月15號(hào)的最近一個(gè)平日。” 假如15號(hào)是星期六,那么 trigger 會(huì)在14號(hào)(星期五)觸發(fā),因?yàn)樾瞧谒谋刃瞧谝浑x15號(hào)更近。
“C”:代表“Calendar”的意思。它的意思是計(jì)劃所關(guān)聯(lián)的日期,如果日期沒(méi)有被關(guān)聯(lián),則相當(dāng)于日歷中所有日期。例如5C在日期字段中就相當(dāng)于日歷5日以后的第一天。1C在星期字段中相當(dāng)于星期日后的第一天。
3.4:一些小案例:
- “0 0 10,14,16 * * ?” 每天上午10點(diǎn),下午2點(diǎn),4點(diǎn)
- “0 0/30 9-17 * * ?” 朝九晚五工作時(shí)間內(nèi)每半小時(shí)
- “0 0 12 ? * WED” 表示每個(gè)星期三中午12點(diǎn)
- “0 0 12 * * ?” 每天中午12點(diǎn)觸發(fā)
- “0 15 10 ? * *” 每天上午10:15觸發(fā)(這個(gè)和下一個(gè)案例說(shuō)明,必須"月份中的日期"和"星期"中有一個(gè)設(shè)置為“?”)
- “0 15 10 * * ?” 每天上午10:15觸發(fā)
- “0 15 10 * * ? *” 每天上午10:15觸發(fā)(7個(gè)元素類型案例,第七個(gè)元素代表年)
- “0 15 10 * * ? 2005” 2005年的每天上午10:15觸發(fā)
- “0 * 14 * * ?” 在每天下午2點(diǎn)到下午2:59期間的每1分鐘觸發(fā)
- “0 0/5 14 * * ?” 在每天下午2點(diǎn)到下午2:55期間的每5分鐘觸發(fā)
- “0 0/5 14,18 * * ?” 在每天下午2點(diǎn)到2:55期間和下午6點(diǎn)到6:55期間的每5分鐘觸發(fā)
- “0 0-5 14 * * ?” 在每天下午2點(diǎn)到下午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” 每月的最后一個(gè)星期五上午10:15觸發(fā)
- “0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一個(gè)星期五上午10:15觸發(fā)
- “0 15 10 ? * 6#3” 每月的第三個(gè)星期五上午10:15觸發(fā)
到這個(gè)地方你應(yīng)該對(duì)@Scheduled有一個(gè)較全面的理解了,下面我們就來(lái)簡(jiǎn)單的看一下其實(shí)現(xiàn)原理吧~
三:原理簡(jiǎn)介
1:主要過(guò)程:
spring在使用applicationContext將類全部初始化。
調(diào)用ScheduledAnnotationBeanPostProcessor類中的postProcessAfterInitialization方法獲取項(xiàng)目中所有被注解 @Scheduled注解的方法 。
通過(guò)processScheduled方法將所有定時(shí)的方法存放在Set tasks = new LinkedHashSet(4); 定時(shí)任務(wù)隊(duì)列中,并解析相應(yīng)的參數(shù)。順序存放,任務(wù)也是順序執(zhí)行。存放順序?yàn)閏ron>fixedDelay>fixedRate
將解析參數(shù)后的定時(shí)任務(wù)存放在一個(gè)初始容量為16 的map中,key為bean name,value為定時(shí)任務(wù):private final Map<Object, Set> scheduledTasks = new IdentityHashMap(16);
之后交給ScheduledTaskRegistrar類的方法scheduleTasks去添加定時(shí)任務(wù)。
2:上述就是一個(gè)大致過(guò)程,下面看一下相應(yīng)的源碼:
注意 :spring對(duì)定時(shí)任務(wù)的操作的源碼全部在spring-context.jar包下的org.springframework.scheduling包下面,主要包含三部分:annotation、config、 support,大家有興趣的話可以去看看
1:獲取項(xiàng)目中所有被注解 @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();
//將獲取的任務(wù)進(jìn)行參數(shù)解析并存放到任務(wù)隊(duì)列
this.processScheduled(scheduled, method, bean);
}
}
...
}
}
return bean;
}
2:通過(guò)processScheduled方法將所有定時(shí)的方法存放在定時(shí)任務(wù)隊(duì)列中
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)) {
...
//存放到任務(wù)隊(duì)列中
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)) {
...
//存放到任務(wù)隊(duì)列中
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)) {
...
//存放到任務(wù)隊(duì)列中
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
Assert.isTrue(processedSchedule, errorMessage);
Map var19 = this.scheduledTasks;
//并發(fā)控制并將任務(wù)存放在map中
synchronized(this.scheduledTasks) {
Set<ScheduledTask> registeredTasks = (Set)this.scheduledTasks.get(bean);
if (registeredTasks == null) {
registeredTasks = new LinkedHashSet(4);
//將任務(wù)存放在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去添加定時(shí)任務(wù)
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));
}
}
}
此部分只是對(duì)原理進(jìn)行了簡(jiǎn)單的介紹,如果有興趣深入了解,可以去看看源碼~
四:其他
做定時(shí)任務(wù)還可以使用java自帶的原生API,Timer和TimerTask去設(shè)計(jì)。
- Timer:一種工具,線程用其安排以后在后臺(tái)線程中執(zhí)行的任務(wù)??砂才湃蝿?wù)執(zhí)行一次,或者定期重復(fù)執(zhí)行。
- TimerTask:定義一個(gè)被執(zhí)行的任務(wù),Timer 安排該任務(wù)為一次執(zhí)行或重復(fù)執(zhí)行的任務(wù)。
可以這樣理解Timer是一種定時(shí)器工具,用來(lái)在一個(gè)后臺(tái)線程計(jì)劃執(zhí)行指定任務(wù),而TimerTask一個(gè)抽象類,它的子類代表一個(gè)可以被Timer計(jì)劃的任務(wù)。
這里就簡(jiǎn)單的提一下,并不是本文的重點(diǎn),具體的用法自行g(shù)oogle吧~
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java設(shè)計(jì)模式之命令模式(Command模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之命令模式(Command模式)介紹,本文講解了Command模式的定義、如何使用命令模式等內(nèi)容,需要的朋友可以參考下2015-03-03
SpringBoot集成P6Spy監(jiān)控SQL執(zhí)行耗時(shí)的詳細(xì)步驟
P6Spy 是一款開(kāi)源的 Java 框架,是一個(gè)強(qiáng)大的數(shù)據(jù)庫(kù)監(jiān)控工具,可以幫助開(kāi)發(fā)者監(jiān)控并分析應(yīng)用程序與數(shù)據(jù)庫(kù)之間的交互行為,本文給大家介紹了SpringBoot集成P6Spy監(jiān)控SQL執(zhí)行耗時(shí)的詳細(xì)步驟,需要的朋友可以參考下2025-07-07
java中servlet實(shí)現(xiàn)登錄驗(yàn)證的方法
做web開(kāi)發(fā),登錄驗(yàn)證是免不了的,今天學(xué)習(xí)了servlet的登錄驗(yàn)證,當(dāng)然是很簡(jiǎn)單的,沒(méi)有使用session,request等作用域?qū)ο螅赃€是可以直接通過(guò)地址訪問(wèn)網(wǎng)頁(yè)的。2013-05-05
Mybatis/Mybatis-Plus駝峰式命名映射的實(shí)現(xiàn)
本文主要介紹了Mybatis-Plus駝峰式命名映射的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
windows系統(tǒng)上如何進(jìn)行maven安裝和配置方式
這篇文章主要介紹了windows系統(tǒng)上如何進(jìn)行maven安裝和配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05
Android中Socket通信的實(shí)現(xiàn)方法概述
這篇文章主要介紹了Android中Socket通信的實(shí)現(xiàn)方法,很有實(shí)用價(jià)值,需要的朋友可以參考下2014-08-08

