Spring中的@Scheduled源碼解析
@Scheduled源碼解析
解析部分
定時(shí)任務(wù)調(diào)度的基礎(chǔ)是ScheduledAnnotationBeanPostProcessor類,這是一個(gè)實(shí)現(xiàn)了BeanPostProcessor接口的后置處理器。
關(guān)于BeanPostProcessor,最主要就是看postProcessBeforeInitialization方法和postProcessAfterInitialization方法做了什么邏輯。 postProcessBeforeInitialization方法沒有實(shí)現(xiàn)邏輯,所以看postProcessAfterInitialization方法的邏輯。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
/**
* 上面省略部分代碼,看下面的關(guān)鍵代碼
*/
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
/**
* 這里一長(zhǎng)串代碼是為了獲取被@Scheduled和@Schedules注解的方法
*/
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
});
//如果沒有被@Scheduled和@Schedules注解的方法,當(dāng)前bean加入到nonAnnotatedClasses集合中,不進(jìn)行處理
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
}
}
else {
//如果存在被@Scheduled和@Schedules注解的方法,針對(duì)每個(gè)方法調(diào)用processScheduled方法
annotatedMethods.forEach((method, scheduledMethods) ->
scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}根據(jù)以上代碼,總結(jié)出ScheduledAnnotationBeanPostProcessor 類做的事情:
(1)獲取被@Scheduled和@Schedules注解標(biāo)記的方法,若沒有,將此Bean加入到nonAnnotatedClasses集合中。
(2)存在被@Scheduled和@Schedules注解的方法,針對(duì)每個(gè)方法調(diào)用processScheduled方法
所以,接下來就是分析關(guān)鍵在于processScheduled方法做的邏輯
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
//將被注解的方法封裝為ScheduledMethodRunnable
Runnable runnable = createRunnable(bean, method);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// 解析initialDelay 的值,字符串和整型值不能同時(shí)配置
long initialDelay = scheduled.initialDelay();
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
if (this.embeddedValueResolver != null) {
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
if (StringUtils.hasLength(initialDelayString)) {
try {
initialDelay = parseDelayAsLong(initialDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
}
}
}
// Check cron expression
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
String zone = scheduled.zone();
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
if (!Scheduled.CRON_DISABLED.equals(cron)) {
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();
}
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
}
// At this point we don't need to differentiate between initial delay set or not anymore
if (initialDelay < 0) {
initialDelay = 0;
}
// Check fixed delay
long fixedDelay = scheduled.fixedDelay();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
if (this.embeddedValueResolver != null) {
fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
}
if (StringUtils.hasLength(fixedDelayString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedDelay = parseDelayAsLong(fixedDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
}
// Check fixed rate
long fixedRate = scheduled.fixedRate();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
if (this.embeddedValueResolver != null) {
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
}
if (StringUtils.hasLength(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedRate = parseDelayAsLong(fixedRateString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
}
// Check whether we had any attribute set
Assert.isTrue(processedSchedule, errorMessage);
// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks);
}
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException(
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}根據(jù)以上代碼,大致邏輯如下:
(1)解析initialDelay的值
(2)根據(jù)@Scheduled注解的屬性配置,分別將此bean的被注解//方法封裝為CronTask,F(xiàn)ixedDelayTask,F(xiàn)ixedRateTask
(3)this.registrar 根據(jù)封裝的任務(wù)類型使用對(duì)應(yīng)的調(diào)度方法scheduleXXX 若此時(shí)taskScheduler局部變量還沒有初始化完成,那么將會(huì)加入到一個(gè)臨時(shí)的集合存起來,不進(jìn)行調(diào)度,這個(gè)taskScheduler可以看做是一個(gè)調(diào)度任務(wù)專用的線程池
(4)調(diào)度方法返回的結(jié)果加入到tasks集合中
(5)然后按照bean分類,放入scheduledTasks集合(以bean為key的Map集合)
其中@Scheduled注解 的限制如下:
(1)cron表達(dá)式不能與initialDelay,fixedDelay,fixedRate一起配置
(2)fixedDelay不能與cron同時(shí)設(shè)置
(3)fixedRate不能與cron 同時(shí)配置
(4)fixedDelay 和fixedRate不能同時(shí)配置
至此postProcessAfterInitialization方法執(zhí)行完成。 粗略總結(jié)一下,這個(gè)方法就是把@Scheduled注解的方法解析出來,然后轉(zhuǎn)化為ScheduledTask,這大概是代表了一個(gè)定時(shí)任務(wù)的對(duì)象,然后再按bean分組存放到一個(gè)Map集合中。
執(zhí)行部分
經(jīng)過驗(yàn)證,其實(shí)上面在執(zhí)行postProcessAfterInitialization方法,taskScheduler還是為null的,也就是說,各個(gè)定時(shí)任務(wù)實(shí)際上還是沒辦法開始調(diào)度執(zhí)行。 舉個(gè)例子:
@Nullable
public ScheduledTask scheduleFixedRateTask(FixedRateTask task) {
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
boolean newTask = false;
if (scheduledTask == null) {
scheduledTask = new ScheduledTask(task);
newTask = true;
}
if (this.taskScheduler != null) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
scheduledTask.future =
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());
}
else {
scheduledTask.future =
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());
}
}
else {
addFixedRateTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}此時(shí)由于taskScheduler 為null,因此沒有執(zhí)行this.taskScheduler.scheduleAtFixedRate方法,而是調(diào)用了addFixedRateTask(task)。(經(jīng)過測(cè)試,就算自定義了taskScheduler,也不會(huì)在這時(shí)候賦值的) 那上面的this.taskScheduler.scheduleAtFixedRate方法 在什么執(zhí)行?帶著這個(gè)疑問,調(diào)試打點(diǎn),最終發(fā)現(xiàn)在onApplicationEvent方法中它才會(huì)執(zhí)行調(diào)度,此時(shí)taskScheduler不為空。
ScheduledAnnotationBeanPostProcessor 類實(shí)現(xiàn)了ApplicationListener接口,監(jiān)聽ContextRefreshedEvent 事件。根據(jù)以前學(xué)習(xí)的Spirng加載流程,ContextRefreshedEvent 事件是Spring容器加載完成之后,執(zhí)行finishRefesh方法時(shí)發(fā)布的。 在監(jiān)聽方法里面主要執(zhí)行了finishRegistration()方法
private void finishRegistration() {
//片段1
if (this.beanFactory instanceof ListableBeanFactory) {
Map<String, SchedulingConfigurer> beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(configurers);
for (SchedulingConfigurer configurer : configurers) {
configurer.configureTasks(this.registrar);
}
}
//省略若干代碼....
this.registrar.afterPropertiesSet();
}有一個(gè)地方,個(gè)人覺得值得了解的: 片段1:自定義調(diào)度線程池時(shí)實(shí)現(xiàn)了SchedulingConfigurer接口 的configureTasks方法,這個(gè)方法就是在片段1執(zhí)行的。
然后之后比較重要的。主要看this.registrar.afterPropertiesSet方法 this.registrar.afterPropertiesSet方法里面調(diào)用了scheduleTasks()方法
protected void scheduleTasks() {
//片段1
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
//片段2
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
addScheduledTask(scheduleFixedDelayTask(task));
}
}
}片段1:this.taskScheduler 如果為null,則使用Executors.newSingleThreadScheduledExecutor()。
如果是自定義線程池,則不會(huì)執(zhí)行,因?yàn)榇藭r(shí)已經(jīng)賦值了。
片段2:根據(jù)不同的定時(shí)任務(wù)類型,分別調(diào)用不同的調(diào)度API
這里的this.cronTasks,this.fixedRateTasks,this.fixedDelayTasks 就是上面執(zhí)行processScheduled方法時(shí),因?yàn)閠his.taskScheduler 為null而把定時(shí)任務(wù)臨時(shí)存放的地方。 因?yàn)楝F(xiàn)在已經(jīng)有this.taskScheduler ,因此正式將它們加入調(diào)度,并放入scheduledTasks 集合中(已經(jīng)參與調(diào)度的不會(huì)重復(fù)加入)。
小結(jié)
(1)從這個(gè)源碼分析,可以知道通過實(shí)現(xiàn)SchedulingConfigurer接口自定義調(diào)度線程池的配置
(2)@Scheduled注解 的限制,不能同時(shí)配置多種任務(wù)類型
到此這篇關(guān)于Spring中的@Scheduled源碼解析的文章就介紹到這了,更多相關(guān)@Scheduled源碼解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot中@Scheduled()注解以及cron表達(dá)式詳解
- Spring 定時(shí)任務(wù)@Scheduled 注解中的 Cron 表達(dá)式詳解
- spring?boot?使用?@Scheduled?注解和?TaskScheduler?接口實(shí)現(xiàn)定時(shí)任務(wù)
- SpringBoot中定時(shí)任務(wù)@Scheduled的多線程使用詳解
- SpringBoot通過@Scheduled實(shí)現(xiàn)定時(shí)任務(wù)及單線程運(yùn)行問題解決
- SpringBoot中定時(shí)任務(wù)@Scheduled注解的使用解讀
相關(guān)文章
Spring中的@CrossOrigin注冊(cè)處理方法源碼解析
這篇文章主要介紹了Spring中的@CrossOrigin注冊(cè)處理方法源碼解析,@CrossOrigin是基于@RequestMapping,@RequestMapping注釋方法掃描注冊(cè)的起點(diǎn)是equestMappingHandlerMapping.afterPropertiesSet(),需要的朋友可以參考下2023-12-12
使用jvm sandbox對(duì)三層嵌套類型的改造示例
這篇文章主要為大家介紹了使用jvm sandbox對(duì)三層嵌套類型的改造示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
基于Java實(shí)現(xiàn)簡(jiǎn)易的七星彩號(hào)碼生成器
七星彩是中國(guó)體育彩票的一種玩法,由中國(guó)國(guó)家體育總局體育彩票管理中心統(tǒng)一發(fā)行。本文為大家準(zhǔn)備了一個(gè)七星彩號(hào)碼生成器Java工具類,感興趣的可以了解一下2022-08-08
詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)
本篇文章主要介紹了詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
MyBatis-Plus實(shí)現(xiàn)多表聯(lián)查的方法實(shí)戰(zhàn)
這篇文章主要給大家介紹了關(guān)于MyBatis-Plus實(shí)現(xiàn)多表聯(lián)查的方法,MyBatis Plus是一款針對(duì)MyBatis框架的增強(qiáng)工具,它提供了很多方便的方法來實(shí)現(xiàn)多表聯(lián)查,需要的朋友可以參考下2023-07-07

