SpringBoot定时任务@Scheduled注解详解
SpringBoot定时任务@Scheduled注解详解
项⽬开发中,经常会遇到定时任务的场景,Spring提供了@Scheduled注解,⽅便进⾏定时任务的开发
概述
要使⽤@Scheduled注解,⾸先需要在启动类添加@EnableScheduling,启⽤Spring的计划任务执⾏功能,这样可以在容器中的任何Spring 管理的bean上检测@Scheduled注解,执⾏计划任务
注解定义
@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-1;
String fixedDelayString()default"";
long fixedRate()default-1;
String fixedRateString()default"";
long initialDelay()default-1;
String initialDelayString()default"";
}
参数说明
参数参数说明⽰例
cron任务执⾏的cron表达式0/1 * * * * ?
zone cron表达时解析使⽤的时区,默认为服务器的本地时区,使⽤java.util.TimeZone#getTimeZone(String)⽅法解析GMT-8:00
fixedDelay上⼀次任务执⾏结束到下⼀次执⾏开始的间隔时间,单位为ms1000 fixedDelayString上⼀次任务执⾏结束到下⼀次执⾏开始的间隔时间,使⽤java.time.Duration#parse解析PT15M
fixedRate 以固定间隔执⾏任务,即上⼀次任务执⾏开始到下⼀次执⾏开始的间隔时间,单位为ms,若在调度任务执⾏时,上⼀次任务还未
执⾏完毕,会加⼊worker队列,等待上⼀次执⾏完成后⽴即执⾏下⼀次任务
2000
fixedRateString与fixedRate逻辑⼀致,只是使⽤java.time.Duration#parse解析PT15M initialDelay⾸次任务执⾏的延迟时间1000 initialDelayString⾸次任务执⾏的延迟时间,使⽤java.time.Duration#parse解析PT15M 源码解析
配置了@Scheduled注解的⽅法,Spring的处理是通过注册ScheduledAnnotationBeanPostProcessor来执⾏,将不同配置参数的任务分配给不同的handler处理,核⼼代码如下
org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(Object bean, String beanName){
if(bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService){
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if(!ains(targetClass)&&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))){
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method ->{
Set<Scheduled> scheduledMethods = MergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return(!scheduledMethods.isEmpty()? scheduledMethods : null);
});
if(annotatedMethods.isEmpty()){
if(logger.isTraceEnabled()){
}
}
else{
// Non-empty set of methods
annotatedMethods.forEach((method, scheduledMethods)->
scheduledMethods.forEach(scheduled ->processScheduled(scheduled, method, bean)));
if(logger.isTraceEnabled()){
"': "+ annotatedMethods);
}
}
}
return bean;
}
org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#processScheduled
/**
* Process the given {@code @Scheduled} method declaration on the given bean.
* @param scheduled the @Scheduled annotation
* @param method the method that the annotation has been declared on
* @param bean the target bean instance
* @see #createRunnable(Object, Method)
*/
protected void processScheduled(Scheduled scheduled, Method method, Object bean){
try{
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);
// 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");
beddedValueResolver != null){
initialDelayString =solveStringValue(initialDelayString);
}
if(StringUtils.hasLength(initialDelayString)){
try{
initialDelay =parseDelayAsLong(initialDelayString);
initialDelay =parseDelayAsLong(initialDelayString);
}
catch(RuntimeException ex){
throw new IllegalArgumentException(
"Invalid initialDelayString value \""+ initialDelayString +"\" - cannot parse into long");
}
}
}
// Check cron expression
String cron = ();
if(StringUtils.hasText(cron)){
String zone = ();
beddedValueResolver != null){
cron =solveStringValue(cron);
zone =solveStringValue(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 = Default();
}
tasks.istrar.scheduleCronTask(new CronTask(runnable,new CronTrigger(cron, timeZone)))); }
springboot aop
}
}
// 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.istrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay))); }
String fixedDelayString = scheduled.fixedDelayString();
if(StringUtils.hasText(fixedDelayString)){
beddedValueResolver != null){
fixedDelayString =solveStringValue(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.istrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay))); }
}
// Check fixed rate
long fixedRate = scheduled.fixedRate();
if(fixedRate >=0){
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule =true;
tasks.istrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
tasks.istrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if(StringUtils.hasText(fixedRateString)){
beddedValueResolver != null){
fixedRateString =solveStringValue(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.istrar.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.scheduledTasksputeIfAbsent(bean, key ->new LinkedHashSet<>(4));  regTasks.addAll(tasks);
}
}
catch(IllegalArgumentException ex){
throw new IllegalStateException(
"Encountered invalid @Scheduled method '"+ Name()+"': "+ ex.getMessage());
}
}
org.fig.ScheduledTaskRegistrar#scheduleTasks
/**
* Schedule all registered tasks against the underlying
* {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.
*/
proected void scheduleTasks(){
if(this.taskScheduler == null){
this.localExecutor = wSingleThreadScheduledExecutor();
this.taskScheduler =new ConcurrentTaskScheduler(this.localExecutor);
}
iggerTasks != null){
for(TriggerTask task :iggerTasks){
addScheduledTask(scheduleTriggerTask(task));
}
}
Tasks != null){
for(CronTask task :Tasks){
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。当项⽬中有多个定时任务时,任务之间会相互等待,同步执⾏源码:
// org.fig.ScheduledTaskRegistrar#scheduleTasks
if(this.taskScheduler == null){
this.localExecutor = wSingleThreadScheduledExecutor();
this.taskScheduler =new ConcurrentTaskScheduler(this.localExecutor);
}
// urrent.Executors#newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor(){
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
代码⽰例:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。