Spring源码情操陶冶#task:scheduled-tasks解析器

承接前文Spring源码情操陶冶#task:executor解析器,在前文基础上解析我们常用的spring中的定时任务的节点配置。备注:此文建立在spring的4.2.3.RELEASE版本

附例

Spring中的定时任务基本配置样例如下

<!--create schedule thread pool-->
<task:scheduler id="baseScheduler" pool-size="5"></task:scheduler>

<!--define bean for schedule task-->
<bean id="taskBean" class="com.jing.test.spring.task.TaskBean"></bean>

<!--apply schedule action to above taskBean-->
<task:scheduled-tasks scheduler="baseScheduler">
    <task:scheduled ref="taskBean" method="doInit" cron="0 0 0 ? * *"></task:scheduled>

    <task:scheduled ref="taskBean" method="doClear" cron="0 0 23 ? * *"></task:scheduled>
</task:scheduled-tasks>

其中task:scheduler的配置是不必须的,并且由上述配置可知Spring配置的定时任务可细化到具体的类方法,有更好的扩展性

task:scheduler节点配置的作用

task:scheduler的节点配置与前文所提及的task:executor节点类似,均是创建线程池,那么有什么不同呢,我们可以稍微简单的看下其解析类org.springframework.scheduling.config.SchedulerBeanDefinitionParser的两个方法

    @Override
    protected String getBeanClassName(Element element) {
        return "org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler";
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String poolSize = element.getAttribute("pool-size");
        if (StringUtils.hasText(poolSize)) {
            builder.addPropertyValue("poolSize", poolSize);
        }
    }

恩,也就是我们直接关注ThreadPoolTaskScheduler这个类即可。看下其是如何创建线程池的

    /**
     * Create a new {@link ScheduledExecutorService} instance.
     * <p>The default implementation creates a {@link ScheduledThreadPoolExecutor}.
     * Can be overridden in subclasses to provide custom {@link ScheduledExecutorService} instances.
     * @param poolSize the specified pool size
     * @param threadFactory the ThreadFactory to use
     * @param rejectedExecutionHandler the RejectedExecutionHandler to use
     * @return a new ScheduledExecutorService instance
     * @see #afterPropertiesSet()
     * @see java.util.concurrent.ScheduledThreadPoolExecutor
     */
    protected ScheduledExecutorService createExecutor(
            int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {

        return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler);
    }

即默认创建的是ScheduledThreadPoolExecutor线程池,创建核心线程个数为pool-size指定的大小(默认为1),最大线程为Integer.MAX_VALUE,队列为DelayedWorkQueue,拒绝策略为AbortPolicy。详情读者可自行阅读

task:sheduled-tasks解析器

配置相应的定时任务,细化到任何bean的方法可直接关联定时器。我们同样观察其主要的两个方法getBeanClassName()doParse()方法

    @Override
    protected String getBeanClassName(Element element) {
        return "org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar";
    }

即实例化并启动定时任务由ContextLifecycleScheduledTaskRegistrar类来执行,我们稍后再谈

    @Override
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        builder.setLazyInit(false); // lazy scheduled tasks are a contradiction in terms -> force to false
        // 存放不同类型定时任务集合
        ManagedList<RuntimeBeanReference> cronTaskList = new ManagedList<RuntimeBeanReference>();
        ManagedList<RuntimeBeanReference> fixedDelayTaskList = new ManagedList<RuntimeBeanReference>();
        ManagedList<RuntimeBeanReference> fixedRateTaskList = new ManagedList<RuntimeBeanReference>();
        ManagedList<RuntimeBeanReference> triggerTaskList = new ManagedList<RuntimeBeanReference>();
        // 解析子节点task:scheduled
        NodeList childNodes = element.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            if (!isScheduledElement(child, parserContext)) {
                continue;
            }
            Element taskElement = (Element) child;
            String ref = taskElement.getAttribute("ref");
            String method = taskElement.getAttribute("method");

            // ref 和 method属性必须同时指定,表示对哪个方法关联定时器
            if (!StringUtils.hasText(ref) || !StringUtils.hasText(method)) {
                parserContext.getReaderContext().error("Both 'ref' and 'method' are required", taskElement);
                // Continue with the possible next task element
                continue;
            }

            String cronAttribute = taskElement.getAttribute("cron");
            String fixedDelayAttribute = taskElement.getAttribute("fixed-delay");
            String fixedRateAttribute = taskElement.getAttribute("fixed-rate");
            String triggerAttribute = taskElement.getAttribute("trigger");
            String initialDelayAttribute = taskElement.getAttribute("initial-delay");

            boolean hasCronAttribute = StringUtils.hasText(cronAttribute);
            boolean hasFixedDelayAttribute = StringUtils.hasText(fixedDelayAttribute);
            boolean hasFixedRateAttribute = StringUtils.hasText(fixedRateAttribute);
            boolean hasTriggerAttribute = StringUtils.hasText(triggerAttribute);
            boolean hasInitialDelayAttribute = StringUtils.hasText(initialDelayAttribute);

            // 必须指定cron/fixed-delay/fixed-rate/trigger属性
            if (!(hasCronAttribute || hasFixedDelayAttribute || hasFixedRateAttribute || hasTriggerAttribute)) {
                parserContext.getReaderContext().error(
                        "one of the 'cron', 'fixed-delay', 'fixed-rate', or 'trigger' attributes is required", taskElement);
                continue; // with the possible next task element
            }

            //initial-delay属性不与cron/trigger属性搭配
            if (hasInitialDelayAttribute && (hasCronAttribute || hasTriggerAttribute)) {
                parserContext.getReaderContext().error(
                        "the 'initial-delay' attribute may not be used with cron and trigger tasks", taskElement);
                continue; // with the possible next task element
            }

            // 将bean类下的method方法包装成ScheduledMethodRunnable.class实体类
            String runnableName =
                    runnableReference(ref, method, taskElement, parserContext).getBeanName();

            if (hasFixedDelayAttribute) {
                // 包装成IntervalTask类
                fixedDelayTaskList.add(intervalTaskReference(runnableName,
                        initialDelayAttribute, fixedDelayAttribute, taskElement, parserContext));
            }
            if (hasFixedRateAttribute) {
                // 包装成IntervalTask类
                fixedRateTaskList.add(intervalTaskReference(runnableName,
                        initialDelayAttribute, fixedRateAttribute, taskElement, parserContext));
            }
            if (hasCronAttribute) {
                // 包装成CronTask类
                cronTaskList.add(cronTaskReference(runnableName, cronAttribute,
                        taskElement, parserContext));
            }
            if (hasTriggerAttribute) {
                // 包装成TriggerTask类
                String triggerName = new RuntimeBeanReference(triggerAttribute).getBeanName();
                triggerTaskList.add(triggerTaskReference(runnableName, triggerName,
                        taskElement, parserContext));
            }
        }
        // scheduler属性
        String schedulerRef = element.getAttribute("scheduler");
        if (StringUtils.hasText(schedulerRef)) {
            builder.addPropertyReference("taskScheduler", schedulerRef);
        }
        builder.addPropertyValue("cronTasksList", cronTaskList);
        builder.addPropertyValue("fixedDelayTasksList", fixedDelayTaskList);
        builder.addPropertyValue("fixedRateTasksList", fixedRateTaskList);
        builder.addPropertyValue("triggerTasksList", triggerTaskList);
    }

代码过长,此处作下小总结

  1. 定时任务的初始化与实例是由org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar类来加载执行
  2. task:scheduled-tasks的子节点名为task:scheduled
  3. task:scheduled中的refmethod属性是必填项;必须指定cron/fixed-delay/fixed-rate/trigger其中之一属性
  4. task-scheduledinitial-delay属性不必填,但其不和cron/trigger属性搭配使用
  5. task:scheduled中的cron代表cron表达式,为字符串形式;fixed-delayfixed-rate可与initial-delay搭配使用,一般选择其中一种即可,为数字形式;trigger代表的是触发器,其关联bean,字符串形式
  6. 根据第五点,具体的任务包装类分别为CronTaskIntervalTaskTriggerTask

ContextLifecycleScheduledTaskRegistrar-定时任务初始化

其默认实现了SmartInitializingSingletonafterSingletonsInstantiated()方法

    @Override
    public void afterSingletonsInstantiated() {
        scheduleTasks();
    }

直接查看父类的schduleTasks()方法

    protected void scheduleTasks() {
        long now = System.currentTimeMillis();
        // 如果不指定scheduler属性,则默认使用单线程池模型
        if (this.taskScheduler == null) {
            this.localExecutor = Executors.newSingleThreadScheduledExecutor();
            this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
        }
        // trigger集合和cron集合统一调用任务定时器的schedule()方法
        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()));
            }
        }
        // fixedRate集合和fixedDelayTasks集合则分别调用任务定时器的scheduleAtFixedRate()和scheduleAtFixedDelay()方法
        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()));
                }
            }
        }
    }

由上述代码可以得出,如果task:scheduled-tasks不指定scheduler属性,则默认会采用org.springframework.scheduling.concurrent.ConcurrentTaskScheduler任务定时器来管理任务集合,反之一般则是由org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler任务定时器来管理任务集合

小结

本文则是解析了task:scheduled-tasktask:scheduler节点的配置,具体的任务是如何被执行的,怎么控制定时任务,请见下文针对org.springframework.scheduling.concurrent.ConcurrentTaskSchedulerorg.springframework.scheduling.concurrent.ThreadPoolTaskScheduler两个任务定时器分别做详细的解读

原文地址:https://www.cnblogs.com/question-sky/p/8733461.html

时间: 2024-11-08 18:21:07

Spring源码情操陶冶#task:scheduled-tasks解析器的相关文章

Spring源码情操陶冶-tx:advice解析器

承接Spring源码情操陶冶-自定义节点的解析.本节关于事务进行简单的解析 spring配置文件样例 简单的事务配置,对save/delete开头的方法加事务,get/find开头的设置为不加事务只读模式 <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save*"

Spring源码分析(十)注册解析的BeanDefinition

摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 对配置文件解析完成后,获取的beanDefiniton已经可以进行使用了,剩下的唯一工作就是注册了,也就是processBeanDefinition方法中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())代码的解析了. /** * Registe

Spring源码学习(四)自定义标签的解析

新的一年 只争朝夕 不负韶华 加油加油?? (一)自定义便签使用 步骤:(前提要将Spring Core包加入项目中) (1)创建需要扩展的组件 (2)定义一个XSD文件描述组件内容 (3)创建一个文件,实现BeanDefinitionParse接口,用来解析XSD文件中的定义和组件定义 (4)创建一个Handler文件,扩展自NamespaceHandleSupport,目的是将组件注册到Spring容器 (5)编写Spring.Handlers和Spring.schemas文件 具体代码如下

spring源码分析之定时任务Scheduled注解

1. @Scheduled 可以将一个方法标识为可定时执行的.但必须指明cron(),fixedDelay(),或者fixedRate()属性. 注解的方法必须是无输入参数并返回空类型void的. @Scheduled注解由注册的ScheduledAnnotationBeanPostProcessor来处理,该processor可以通过手动来注册,更方面的方式是通过<task:annotation-driven/>或者@EnableScheduling来注册.

Spring源码学习(三)默认标签的解析

默认标签的解析分为四种:import,alias,bean,beans,在下面函数中进行 1 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { 2 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { 3 importBeanDefinitionResource(ele); 4 } 5 else if (delegate.n

spring 源码如何导入到eclipse

官方说明: https://github.com/spring-projects/spring-framework 先讲基本步骤,如下: 去GitHub中下载spring framework的源码,https://github.com/spring-projects/spring-framework/releases 下载3.2.X其中的一个版本; 安装Gradle软件,官网下载,解压即可,设置GRADLE_HOME,和PATH. 命令行中运行gradle -v,检查一下是否正确安装配置: 命令

spring源码--容器的基本实现

在工作中见得非常多的容器使用是: <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springfra

Spring源码系列 — BeanDefinition扩展点

前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition,再到容器扩展点,最后到Bean创键,这个过程中无处不存在Spring预留的扩展口. 本篇文章介绍Spring的另一种扩展点:BeanDefinition扩展点,该扩展点是为处理BeanDefinition而设计.本文主要从以下几点分析: BeanDefinition扩展点的几种方式 BeanDef

Spring源码分析 之浅谈设计模式

一直想专门写个Spring源码的博客,工作了,可以全身性的投入到互联网行业中.虽然加班很严重,但是依然很开心.趁着凌晨有时间,总结总结. 首先spring,相信大家都很熟悉了. 1.轻量级  零配置,API使用简单 2.面向Bean  只需要编写普通的Bean(一个Bean代表一个对象) 3.松耦合 充分利用AOP思想 )(各自可以独立开发,然后整合起来运行) 4.万能胶 与主流框架无缝集成 (Mybatis dubbo等等 ) 5.设计模式 将Java中经典的设计模式运用的淋漓尽致 Sprin