spring 定时任务@Scheduled原理解析

2023-03-27 14:15:57

https://zhuanlan.zhihu.com/p/92768652

官网API定义:

An annotation that marks a method to be scheduled. Exactly one of thecron(),fixedDelay(), orfixedRate()attributes must be specified.

The annotated method must expect no arguments. It will typically have a void return type; if not, the returned value will be ignored when called through the scheduler.

Processing of @Scheduled annotations is performed by registering a ScheduledAnnotationBeanPostProcessor. This can be done manually or, more conveniently, through the <task:annotation-driven/> element or @EnableScheduling annotation.

原理解析

1.定义

public @interface Scheduled {
    String CRON_DISABLED = "-";
    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 :来源于linux,注释是描述任务执行触发的方式的
  • zone:描述时区,因为不同的地方时区不一致
  • fixedDelay:固定间隔,假设任务从 0s 开始执行,10s 执行一次,但是任务执行了12s 那么下次的执行时间就是 22s,即:就是两次任务的固定的间隔
  • fixedRate:固定的频率,假设任务从 0s 执行,10s 执行一次,但是任务执行12s,那么下次执行的时间是 12s

2.Scheduled代码执行原理说明

描述:spring在初始化bean后,通过“postProcessAfterInitialization”拦截到所有的用到“@Scheduled”注解的方法,并解析相应的的注解参数,放入“定时任务列表”等待后续处理;之后再“定时任务列表”中统一执行相应的定时任务,默认是单线程执行,可以通过扩展scheduleConfig.java来自定义线程池,使其支持多个定时任务的并发。

涉及代码块

//ScheduledAnnotationBeanPostProcessor来执行解析逻辑,参考spring-context 5.0.7
public Object postProcessAfterInitialization(Object bean, String beanName) {
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        if (!this.nonAnnotatedClasses.contains(targetClass)) {
            Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (method) -> {
                Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);
                return !scheduledMethods.isEmpty() ? scheduledMethods : null;
            });
            if (annotatedMethods.isEmpty()) {
                this.nonAnnotatedClasses.add(targetClass);
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("No @Scheduled annotations found on bean class: " + bean.getClass());
                }
            } else {
                annotatedMethods.forEach((method, scheduledMethods) -> {
                    scheduledMethods.forEach((scheduled) -> {
                        this.processScheduled(scheduled, method, bean);
                    });
                });
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods);
                }
            }
        }

        return bean;
    }

解析方法中包含@schedule注解的方法,将其添加到处理任务中。

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
        try {
            Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");
            Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
            Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
            boolean processedSchedule = false;
            String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
            Set<ScheduledTask> tasks = new LinkedHashSet(4);
            long initialDelay = scheduled.initialDelay();
            String initialDelayString = scheduled.initialDelayString();
            if (StringUtils.hasText(initialDelayString)) {
                Assert.isTrue(initialDelay < 0L, "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 var25) {
                        throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
                    }
                }
            }
....
}

解析配置内容,添加到不同的定时任务中

@FunctionalInterface
public interface SchedulingConfigurer {
    void configureTasks(ScheduledTaskRegistrar var1);

//自定义集成此接口,实现多线程并发执行任务
public class ScheduleConfig implements SchedulingConfigurer{
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        //开启多线程执行定时任务,默认只有单线程执行
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
    }
}

执行定时任务

private void finishRegistration() {
        if (this.scheduler != null) {
            this.registrar.setScheduler(this.scheduler);
        }

        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);
            Iterator var3 = configurers.iterator();

            while(var3.hasNext()) {
                SchedulingConfigurer configurer = (SchedulingConfigurer)var3.next();
                configurer.configureTasks(this.registrar);
            }
        }
        ...
        try {
                //执行定时任务
                this.registrar.setTaskScheduler((TaskScheduler)this.resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
            }
        ...
}

总结:spring自带的定时任务操作简单,代码开发量相对较少,主要以配置为主,quartz集成更复杂的功能,根据业务场景来选择合适组件。

  • 作者:fyygree
  • 原文链接:https://blog.csdn.net/fengyuyeguirenenen/article/details/124337251
    更新时间:2023-03-27 14:15:57