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来完成定时任务。
微信公众号: 架构师日常笔记 欢迎关注!