spring源码分析之定时任务Scheduled注解
1. @Scheduled 可以将一个方法标识为可定时执行的。但必须指明cron(),fixedDelay(),或者fixedRate()属性。
注解的方法必须是无输入参数并返回空类型void的。
@Scheduled注解由注册的ScheduledAnnotationBeanPostProcessor来处理,该processor可以通过手动来注册,更方面的方式是通过<task:annotation-driven/>或者@EnableScheduling来注册。@EnableScheduling可以注册的原理是什么呢?先看定义:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling { }
可以看到@EnableScheduling的实现由SchedulingConfiguration来完成。
@Configuration
public class SchedulingConfiguration { @Bean(name = AnnotationConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
} }
从上述代码可以看出,SchedulingConfiguration注册了一个ScheduledAnnotationBeanPostProcessor。
来看一下ScheduledAnnotationBeanPostProcessor来如何处理定时任务的?
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
        try {
            Assert.isTrue(void.class.equals(method.getReturnType()),
                    "Only void-returning methods may be annotated with @Scheduled");
            Assert.isTrue(method.getParameterTypes().length == 0,
                    "Only no-arg methods may be annotated with @Scheduled");
            if (AopUtils.isJdkDynamicProxy(bean)) {
                try {
                    // Found a @Scheduled method on the target class for this JDK proxy ->
                    // is it also present on the proxy itself?
                    method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
                }
                catch (SecurityException ex) {
                    ReflectionUtils.handleReflectionException(ex);
                }
                catch (NoSuchMethodException ex) {
                    throw new IllegalStateException(String.format(
                            "@Scheduled method '%s' found on bean target class '%s' but not " +
                            "found in any interface(s) for a dynamic proxy. Either pull the " +
                            "method up to a declared interface or switch to subclass (CGLIB) " +
                            "proxies by setting proxy-target-class/proxyTargetClass to 'true'",
                            method.getName(), method.getDeclaringClass().getSimpleName()));
                }
            }
            else if (AopUtils.isCglibProxy(bean)) {
                // Common problem: private methods end up in the proxy instance, not getting delegated.
                if (Modifier.isPrivate(method.getModifiers())) {
                    LogFactory.getLog(ScheduledAnnotationBeanPostProcessor.class).warn(String.format(
                            "@Scheduled method '%s' found on CGLIB proxy for target class '%s' but cannot " +
                            "be delegated to target bean. Switch its visibility to package or protected.",
                            method.getName(), method.getDeclaringClass().getSimpleName()));
                }
            }
            Runnable runnable = new ScheduledMethodRunnable(bean, method);
            boolean processedSchedule = false;
            String errorMessage =
                    "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
            // Determine initial delay
            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);
                }
                try {
                    initialDelay = Integer.parseInt(initialDelayString);
                }
                catch (NumberFormatException ex) {
                    throw new IllegalArgumentException(
                            "Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
                }
            }
            // Check cron expression
            String cron = scheduled.cron();
            if (StringUtils.hasText(cron)) {
                Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
                processedSchedule = true;
                String zone = scheduled.zone();
                if (this.embeddedValueResolver != null) {
                    cron = this.embeddedValueResolver.resolveStringValue(cron);
                    zone = this.embeddedValueResolver.resolveStringValue(zone);
                }
                TimeZone timeZone;
                if (StringUtils.hasText(zone)) {
                    timeZone = StringUtils.parseTimeZoneString(zone);
                }
                else {
                    timeZone = TimeZone.getDefault();
                }
                this.registrar.addCronTask(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;
                this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
            }
            String fixedDelayString = scheduled.fixedDelayString();
            if (StringUtils.hasText(fixedDelayString)) {
                Assert.isTrue(!processedSchedule, errorMessage);
                processedSchedule = true;
                if (this.embeddedValueResolver != null) {
                    fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
                }
                try {
                    fixedDelay = Integer.parseInt(fixedDelayString);
                }
                catch (NumberFormatException ex) {
                    throw new IllegalArgumentException(
                            "Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
                }
                this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
            }
            // Check fixed rate
            long fixedRate = scheduled.fixedRate();
            if (fixedRate >= 0) {
                Assert.isTrue(!processedSchedule, errorMessage);
                processedSchedule = true;
                this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
            }
            String fixedRateString = scheduled.fixedRateString();
            if (StringUtils.hasText(fixedRateString)) {
                Assert.isTrue(!processedSchedule, errorMessage);
                processedSchedule = true;
                if (this.embeddedValueResolver != null) {
                    fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
                }
                try {
                    fixedRate = Integer.parseInt(fixedRateString);
                }
                catch (NumberFormatException ex) {
                    throw new IllegalArgumentException(
                            "Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
                }
                this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
            }
            // Check whether we had any attribute set
            Assert.isTrue(processedSchedule, errorMessage);
        }
        catch (IllegalArgumentException ex) {
            throw new IllegalStateException(
                    "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
        }
    }
从上面的代码可以看出:@Scheduled有三个属性,分别是:
cron expression
fixedDelay
fixedRate
根据这些属性的不同,都加入到ScheduledTaskRegistrar来管理定时任务:
/**
* Schedule all registered tasks against the underlying {@linkplain
* #setTaskScheduler(TaskScheduler) task scheduler}.
*/
protected void scheduleTasks() {
long now = System.currentTimeMillis(); if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
this.scheduledFutures.add(this.taskScheduler.schedule(
task.getRunnable(), task.getTrigger()));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
this.scheduledFutures.add(this.taskScheduler.schedule(
task.getRunnable(), task.getTrigger()));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(now + task.getInitialDelay());
this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
task.getRunnable(), startTime, task.getInterval()));
}
else {
this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate(
task.getRunnable(), task.getInterval()));
}
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(now + task.getInitialDelay());
this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
task.getRunnable(), startTime, task.getInterval()));
}
else {
this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(
task.getRunnable(), task.getInterval()));
}
}
}
}
从上面看出:
3种不同属性的task均由quartz的taskScheduler的不同方法来完成,
scheduleWithFixedDelay,
scheduleAtFixedRate,
schedule
即最终的实现由TaskScheduler来完成定时任务。
spring源码分析之定时任务Scheduled注解的更多相关文章
- Spring 3.1新特性之二:@Enable*注解的源码,spring源码分析之定时任务Scheduled注解
		分析SpringBoot的自动化配置原理的时候,可以观察下这些@Enable*注解的源码,可以发现所有的注解都有一个@Import注解.@Import注解是用来导入配置类的,这也就是说这些自动开启的实 ... 
- spring源码分析之定时任务概述
		Spring框架提供了TaskExcutor的异步执行和TashScheduler的任务定时执行接口,同样spring也提供了线程池或者CommonJ的代理. TaskExecutor的类型 Simp ... 
- spring源码分析之spring-core总结篇
		1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ... 
- spring源码分析(二)Aop
		创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ... 
- 【spring源码分析】IOC容器初始化(一)
		前言:spring主要就是对bean进行管理,因此IOC容器的初始化过程非常重要,搞清楚其原理不管在实际生产或面试过程中都十分的有用.在[spring源码分析]准备工作中已经搭建好spring的环境, ... 
- 【spring源码分析】IOC容器初始化(十)
		前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ... 
- Spring 源码分析之 bean 依赖注入原理(注入属性)
		最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ... 
- spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析
		更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ... 
- spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor
		更多文章点击--spring源码分析系列 主要分析内容: 一.BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor简述与demo示例 ... 
随机推荐
- 《JavaScript Dom编程艺术》(第二版)
			第一章<JavaScript简史> 1.JavaScript是一种脚本语言,通常只能通过Web浏览器去完成一些操作而不能像普通意义上的程序那样独立运行,它需要由Web浏览器进行解释和执行. ... 
- <!DOCTYPE html>很重要
			噩梦开始的源头:之前写html或者jsp页面,从来不注意doctype的声明,也不太明白doctype的作用.直到最近碰到了一个非常奇葩的 bug:某一个页面在IE7和8,Chrome,ff等下正常, ... 
- DSY3163*Eden的新背包问题
			Description "寄没有地址的信,这样的情绪有种距离,你放着谁的歌曲,是怎样的心心静,能不能说给我听."失忆的Eden总想努力地回忆起过去,然而总是只能清晰地记得那种思念的 ... 
- Oracle中的CHR()函数与ASCII()函数
			工作中经常会处理一些因特殊字符而导致的错误,如上周我就遇到了因为换行符和回车符导致的数据上报的错误,这种错误比较难以发现,通常是由于用户的输入习惯导致的,有可能数据极少,就那么几行错误从而导致整个数据 ... 
- 控制TextField的内容长度
			参考如下代码(下例是控制设置交易密码,控制6位): - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [ ... 
- Android课程---关于Service的学习(后台运行)
			MainActivity.java package com.hanqi.test2; import android.content.ComponentName; import android.cont ... 
- CSS 使用母版页的内容页如何调用css和javascript
			方案一: 把所有的css样式和javascript函数放到母版页的<head></head>中,我觉得这样做的弊端就是导致母版页的<head></head&g ... 
- International Conference for Smart Health 2015 Call for Papers
			Advancing Informatics for healthcare and healthcare applications has become an international researc ... 
- 简化 Web 应用程序与 Windows Azure Active Directory、ASP.NET 和 Visual Studio 的集成
			大家好! 今天的博文深入讨论我们今天推出的开发人员工具和框架中的一些新功能.我们通过与 ASP.NET 和 Visual Studio 团队合作开发了一些重大的增强功能,让开发人员能够轻松使用 Win ... 
- Android5.1.1 - APK签名校验分析和修改源码绕过签名校验
			Android5.1.1 - APK签名校验分析和修改源码绕过签名校验 作者:寻禹@阿里聚安全 APK签名校验分析 找到PackageParser类,该类在文件“frameworks/base/cor ... 
