领略Quartz源码架构之美——源码实弹之Scheduler(一)

本章阅读收获:可了解Quartz框架中的Scheduler部分源码

看得见的手

有了需要运行的任务Job,和确定运行时间的Trigger之后,目前可以说这两部分是分开的,互补关联,这时候需要有一只手来操控这一切,那就是Scheduler。

Schedule是什么

英文意思大家应该都知道,就是计划、时间表等等。而在Quartz,这就是那只大手。让我们在回顾一下之前的demo:

/**
 * 演示01任务调度器
 * 2018/11/14 编写
 */
public class Exemple01Schedule {
  /**
   * 计划工厂类
   */
  private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();

  /**
   * 任务名称
   */
  private static String JOB_GROUP_NAME = "job-group-1";

  /**
   * 触发器名称
   */
  private static String TRIGGER_GROUP_NAME = "trigger-group-1";

  /**
   * 增加任务
   * @param jobClass 任务实现类
   * @param jobName 任务名称
   * @param interval 间隔时间
   * @param data 数据
   */
  public static void addJob(Class jobClass, String jobName, int interval, Map data){
    try {
      //获取任务调度器
      Scheduler scheduler = schedulerFactory.getScheduler();
      //获取任务详细信息
      JobDetail jobDetail = JobBuilder.newJob(jobClass)
          //任务名称和组构成任务key
          .withIdentity(jobName, JOB_GROUP_NAME)
          .build();
      jobDetail.getJobDataMap().putAll(data);
      // 触发器
      SimpleTrigger trigger = TriggerBuilder.newTrigger()
          //触发器key唯一标识
          .withIdentity(jobName, TRIGGER_GROUP_NAME)
          //调度开始时间
          .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
          //调度规则
          .withSchedule(SimpleScheduleBuilder.simpleSchedule()
              .withIntervalInSeconds(interval)
              .repeatForever())
          .build();

      scheduler.scheduleJob(jobDetail, trigger);
      // 启动
      if (!scheduler.isShutdown()) {
        scheduler.start();
      }
    } catch (SchedulerException e) {
      System.out.println(e.getMessage());
    }
  }
}

第一个进入眼帘的就是SchedulerFactory,下面就来进入分析。

SchedulerFactory源码分析

首先我们照常例先看下SchedulerFactory的源码:

package org.quartz;

import java.util.Collection;

/**
 * 调度器工厂类
 */
public interface SchedulerFactory {

    /**
     * 获取任务调度器
     */
    Scheduler getScheduler() throws SchedulerException;

    /**
     * 根据调度器名称获取任务调度器
     */
    Scheduler getScheduler(String schedName) throws SchedulerException;

    /**
     * 获取所有调度器
     */
    Collection getAllSchedulers() throws SchedulerException;

}

大家不知有没有注意到,在我们new的时候,创建的实例其实是StdSchedulerFactory类,那么我们接下来就以这个类来进行分析。

StdSchedulerFactory源码分析

这里代码有点长,所有我只能逐步进行分析:首先我们可以把焦点关注于这个类的属性:

    /**
     * 调取器异常
     */
    private SchedulerException initException = null;

    /**
     * 配置普京
     */
    private String propSrc = null;

    /**
     * 配置属性
     */
    private PropertiesParser cfg;

这里属性其实只有3个,下面我们来看下在demo中的调用方法:

/**
     * 获取任务Schedule
     */
    @Override
    public Scheduler getScheduler() throws SchedulerException {
        //第一步加载配置文件,System的properties覆盖前面的配置
        if (cfg == null) {
            initialize();
        }
        //本地存储Schedule任务,注:SchedulerRepository是单例模式
        SchedulerRepository schedRep = SchedulerRepository.getInstance();
        //从缓存中查询获取Schedule任务,任务名称从配置中获取,若无指定,则默认指定QuartzScheduler
        Scheduler sched = schedRep.lookup(getSchedulerName());
        //判断若存在且已停止运行,则从缓存中移除
        if (sched != null) {
            if (sched.isShutdown()) {
                schedRep.remove(getSchedulerName());
            } else {
                return sched;
            }
        }
        //开始很多初始化对象
        sched = instantiate();

        return sched;
    }

由于这个时候cfg为null,所以我们需要跳到initialize方法:

 /**
     * 根据配置文件初始化
     */
    public void initialize() throws SchedulerException {
        // 如果已经存在,直接返回
        if (cfg != null) {
            return;
        }
        if (initException != null) {
            throw initException;
        }
        //PROPERTIES_FILE = org.quartz.properties 是否存在指定读取的配置文件
        String requestedFile = System.getProperty(PROPERTIES_FILE);
        //不主动设置,默认设置为quartz.properties
        String propFileName = requestedFile != null ? requestedFile
                : "quartz.properties";
        File propFile = new File(propFileName);

        Properties props = new Properties();

        InputStream in = null;
        //读取配置文件内容,如果都不存在依次读取quartz.properties、/quartz.properties、org/quartz/quartz.properties
        try {
            if (propFile.exists()) {
                try {
                    if (requestedFile != null) {
                        propSrc = "specified file: '" + requestedFile + "'";
                    } else {
                        propSrc = "default file in current working dir: 'quartz.properties'";
                    }

                    in = new BufferedInputStream(new FileInputStream(propFileName));
                    props.load(in);

                } catch (IOException ioe) {
                    initException = new SchedulerException("Properties file: '"
                            + propFileName + "' could not be read.", ioe);
                    throw initException;
                }
            } else if (requestedFile != null) {
                in =
                    Thread.currentThread().getContextClassLoader().getResourceAsStream(requestedFile);

                if(in == null) {
                    initException = new SchedulerException("Properties file: '"
                        + requestedFile + "' could not be found.");
                    throw initException;
                }

                propSrc = "specified file: '" + requestedFile + "' in the class resource path.";

                in = new BufferedInputStream(in);
                try {
                    props.load(in);
                } catch (IOException ioe) {
                    initException = new SchedulerException("Properties file: '"
                            + requestedFile + "' could not be read.", ioe);
                    throw initException;
                }

            } else {
                propSrc = "default resource file in Quartz package: 'quartz.properties'";

                ClassLoader cl = getClass().getClassLoader();
                if(cl == null)
                    cl = findClassloader();
                if(cl == null)
                    throw new SchedulerConfigException("Unable to find a class loader on the current thread or class.");

                in = cl.getResourceAsStream(
                        "quartz.properties");

                if (in == null) {
                    in = cl.getResourceAsStream(
                            "/quartz.properties");
                }
                if (in == null) {
                    in = cl.getResourceAsStream(
                            "org/quartz/quartz.properties");
                }
                if (in == null) {
                    initException = new SchedulerException(
                            "Default quartz.properties not found in class path");
                    throw initException;
                }
                try {
                    props.load(in);
                } catch (IOException ioe) {
                    initException = new SchedulerException(
                            "Resource properties file: 'org/quartz/quartz.properties' "
                                    + "could not be read from the classpath.", ioe);
                    throw initException;
                }
            }
        } finally {
            if(in != null) {
                try { in.close(); } catch(IOException ignore) { /* ignore */ }
            }
        }
        //赋值
        initialize(overrideWithSysProps(props));
    }

有关于配置的我们放到之后在进行分析,现在我们按照demo的调用方式,很自然的可以读到我们的调用代码块是:

                propSrc = "default resource file in Quartz package: 'quartz.properties'";

                ClassLoader cl = getClass().getClassLoader();
                if(cl == null)
                    cl = findClassloader();
                if(cl == null)
                    throw new SchedulerConfigException("Unable to find a class loader on the current thread or class.");

                in = cl.getResourceAsStream(
                        "quartz.properties");

                if (in == null) {
                    in = cl.getResourceAsStream(
                            "/quartz.properties");
                }
                if (in == null) {
                    in = cl.getResourceAsStream(
                            "org/quartz/quartz.properties");
                }
                if (in == null) {
                    initException = new SchedulerException(
                            "Default quartz.properties not found in class path");
                    throw initException;
                }
                try {
                    props.load(in);
                } catch (IOException ioe) {
                    initException = new SchedulerException(
                            "Resource properties file: 'org/quartz/quartz.properties' "
                                    + "could not be read from the classpath.", ioe);
                    throw initException;
                }

可能大家会好奇,我明明按照代码读下来,我们并没有设置quartz.properties的配置文件,照道理来讲应该会抛出异常呀?但是我这程序为什么还能顺利运行呢?不知大家注意到没有:

  if (in == null) {
                    in = cl.getResourceAsStream(
                            "org/quartz/quartz.properties");
                }

我们这里会去拿源码里面的默认配置文件,然后就是加载到配置属性中。至于会加载什么属性,目前不做解析。

然后就是overrideWithSysProps方法,

 /**
     * 添加系统配置,如果跟之前的配置相同则覆盖,以系统配置为主
     */
    private Properties overrideWithSysProps(Properties props) {
        Properties sysProps = null;
        try {
            sysProps = System.getProperties();
        } catch (AccessControlException e) {
            getLog().warn(
                "Skipping overriding quartz properties with System properties " +
                "during initialization because of an AccessControlException.  " +
                "This is likely due to not having read/write access for " +
                "java.util.PropertyPermission as required by java.lang.System.getProperties().  " +
                "To resolve this warning, either add this permission to your policy file or " +
                "use a non-default version of initialize().",
                e);
        }

        if (sysProps != null) {
            props.putAll(sysProps);
        }

        return props;
    }

相信大家都看的懂,就是单纯的覆盖配置属性。而之后就是:

public void initialize(Properties props) throws SchedulerException {
        if (propSrc == null) {
            propSrc = "an externally provided properties instance.";
        }

        this.cfg = new PropertiesParser(props);
    }

这次就先看到这,我们之后继续再往下分析。

结束语

本文阅读完之后,我们可以大致了解到Schedule的功能,和部分涉及到的源码,下文我们将继续展开分析。

你可能感兴趣的:(领略Quartz源码架构之美——源码实弹之Scheduler(一))