Spring中的定时器都会了?

前言

  日常并不是需要所有的服务是都是立即需要返回给客户端的响应的,有一些任务是需要定时定点的去执行,这个时候我们想到的就是定时器的用处了,定时器的编写是很简单的。但是有的时候不了解其中的原理是很容易发生一些小问题(例如他们的线程池共用)。

解析过程

  本地准备了一个小demo。里面有个简单的定时器,要求是每一分钟执行一次。然后启动类上需要加上@EnableScheduling注解信息。
  1. 代码部分

    @Component
    public class TimeScheduled {
        @Scheduled(cron = "* 0/1 * * * ?")
        public void test(){
            System.out.println("yyy");
        }
    }
  2. 主要注意的是@EnableScheduling注解,里面引入了一个后处理器ScheduledAnnotationBeanPostProcessor。这里就是解析Bean将其任务封装。
    主要方法:

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (!(bean instanceof AopInfrastructureBean) && !(bean instanceof TaskScheduler) && !(bean instanceof ScheduledExecutorService)) { // 不属于组件bean方可进入
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); // 获取最终的bean(可能在此之前被代理了)
            if (!this.nonAnnotatedClasses.contains(targetClass)) {
            // 获取Scheduled方法集合
                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: " + targetClass);
                    }
                } else {
                    // 循环遍历解析表达式  并将定时任务注册到ScheduledTaskRegistrar中
                    annotatedMethods.forEach((method, scheduledMethods) -> {
                        scheduledMethods.forEach((scheduled) -> {
                            this.processScheduled(scheduled, method, bean);
                        });
                    });
                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods);
                    }
                }
            }
            return bean;
        } else {
            return bean;
        }
  3. 解析之后会将对应的定时方法根据类型封装到org.springframework.scheduling.config.ScheduledTaskRegistrar中,里面有以下几类任务集合。

    private List<TriggerTask> triggerTasks; // 触发器任务  本次未使用
    private List<CronTask> cronTasks; //  含有cron表达式的任务
    private List<IntervalTask> fixedRateTasks; // 含有fixedRate的任务
    private List<IntervalTask> fixedDelayTasks; // 含有fixedDelay的任务
    private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap(16); // 作为全集任务集合,添加完上面的任务之后会往这个集合中丢入
    private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet(16); // 执行之后的任务集合
  4. 将任务添加到集合中,那么什么时候会进行任务的执行呢?我们可以看到这个bean后处理器还实现了ApplicationListener接口。然后深入之后发现他调用了ScheduledTaskRegistrarafterPropertiesSet方法,该方法里面调用了对应的执行过程。

    Spring中的定时器都会了?

  5. 之后ScheduledExecutorService再根据org.springframework.scheduling.support.CronTrigger#nextExecutionTime计算出来的下次时间进行任务执行。
    Spring中的定时器都会了?

常见问题

  1. 多个任务定时器有交叉时间,却发现有些任务排队到时间未执行(采用默认线程池配置)

    这块问题跟着源码进去就能发现,在refresh阶段创建taskScheduler这个Bean的时候,org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler里面默认的线程数大小为1,一旦任务执行开始的时候,按照线程池执行过程,剩下的任务就会丢到队列中。

补充疑惑

看了看定时器里面的代码,可能认知不够深,对此还是有些疑问的。
  1. 这里不知道小伙伴有没有疑问(我是有点疑问的)?因为ScheduledTaskRegistrar类实现了InitializingBean接口,那为何不在容器执行该步骤的时候自动调用,还为何还利用上下文事件监听然后再进行方法调用?

    看到框架中该ScheduledTaskRegistrar类不是容器Bean,虽然实现了对应的接口,但是不在Spring启动解析中调用。如果你单独的将该类进行Bean化,那么此时的Bean是容器内部的,而不是与定时相关联的,那么执行的时候因为没有元素而不会进行执行。同时再解析InitializingBean以及执行里面的方法的时候,对应的Bean还未完成初始化操作(一个是finishBeanFactoryInitialization步骤执行,一个是在finishRefresh执行)。(个人观点其实可以直接去掉该InitializingBean实现,毕竟用不到,让该类看起来更专一点)

总结

这段时间看了许多源码,感慨下有些东西还是需要你下去看看的,下面的世界更精彩,或许你都能回答出面试官等等的问题,但是一旦追问,你是否知道底下怎么实现?为何这么设计? 让你设计是如何做? (条条大路通罗马,这是我目前的路~)
上一篇:Spring中的DeferredResult执行


下一篇:vue项目中axios封装总结