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来完成定时任务。

 

posted on 2016-07-18 09:32  一天不进步,就是退步  阅读(16055)  评论(0编辑  收藏  举报