领略Quartz源码架构之美——源码实弹之Scheduler(一
2018-11-19 本文已影响0人
向光奔跑_
本章阅读收获:可了解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<? extends Job> jobClass, String jobName, int interval, Map<String, Object> 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<Scheduler> 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的功能,和部分涉及到的源码,下文我们将继续展开分析。