Quartz所有都起始于SchedulerFactory
, SchedulerFactory
有两个是实现类StdSchedulerFactory
和DirectSchedulerFactory
。
StdSchedulerFactory
:使用一组属性(java.util.Properties)来创建和初始化Quartz Scheduler。属性通常存储在文件中并从文件中加载,但也可以由程序创建并直接传递到工厂。DirectSchedulerFactory
:以更加程序化的方式创建其Scheduler实例,即所有的参数都需要传递。
这里以StdSchedulerFactory为例描述Quartz初始化的整个过程:
public static Scheduler getDefaultScheduler() throws SchedulerException {
StdSchedulerFactory fact = new StdSchedulerFactory();
return fact.getScheduler();
}
StdSchedulerFactory
中提供一个getDefaultScheduler
的方法,获取一个默认的Scheduler。
这里直接new一个StdSchedulerFactory,并调用了getScheduler
方法。
public Scheduler getScheduler() throws SchedulerException {
if (cfg == null) {
//第一步加载配置文件,System的properties覆盖前面的配置
initialize();
}
// 本地存储Schedule任务,注:SchedulerRepository是单例模式
SchedulerRepository schedRep = SchedulerRepository.getInstance();
Scheduler sched = schedRep.lookup(getSchedulerName());
if (sched != null) {
if (sched.isShutdown()) {
schedRep.remove(getSchedulerName());
} else {
return sched;
}
}
//开始很多初始化对象
sched = instantiate();
return sched;
}
获取配置文件
判断PropertiesParser
是否为空,不为空就说明已经初始化过,不需要再次初始化。如果没有初始化,直接调用initialize
方法初始化,加载配置文件。
public void initialize() throws SchedulerException {
// short-circuit if already initialized
// 如果已初始化
if (cfg != null) {
return;
}
if (initException != null) {
throw initException;
}
String requestedFile = System.getProperty(PROPERTIES_FILE);
String propFileName = requestedFile != null ? requestedFile
: "quartz.properties";
File propFile = new File(propFileName);
Properties props = new Properties();
InputStream in = null;
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));
}
再次判断是否初始化,有异常抛异常,并开始读取配置文件数据,默认quartz.properties
,配置key:org.quartz.properties
,总而言之,这段代码就是找出quartz的配置文件,读取到Properties中。
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;
}
再来看一下最后的overrideWithSysProps
方法,顾名思义,通过获取系统变量覆盖配置文件中的配置,所以这里系统配置优先级高于配置文件。
public void initialize(Properties props) throws SchedulerException {
if (propSrc == null) {
propSrc = "an externally provided properties instance.";
}
this.cfg = new PropertiesParser(props);
}
除了overrideWithSysProps
,最后哦一行还有一个initialize
方法,这里就是初始化PropertiesParser全局对象。
至此,加载配置文件的过程全部结束。
创建Scheduler
继续回到 getScheduler
方法
SchedulerRepository
类似一个本地缓存,Map类型存储<SchedulerName,Scheduler>,先判断SchedulerRepository
存不存在,不存在就新增,再判断SchedulerRepository
中是否存在当前需要加载的Scheduler,如果存在,并且不是停止的就不用往下走了,直接返回。如果是停止的,需要先从缓存中移除,然后进行初始化加载。接下来进入核心的instantiate方法。
核心方法instantiate
1、获取基础配置
if (cfg == null) {
initialize();
}
if (initException != null) {
throw initException;
}
JobStore js = null;
ThreadPool tp = null;
QuartzScheduler qs = null;
DBConnectionManager dbMgr = null;
String instanceIdGeneratorClass = null;
Properties tProps = null;
String userTXLocation = null;
boolean wrapJobInTx = false;
boolean autoId = false;
long idleWaitTime = -1;
long dbFailureRetry = 15000L; // 15 secs
String classLoadHelperClass;
String jobFactoryClass;
ThreadExecutor threadExecutor;
SchedulerRepository schedRep = SchedulerRepository.getInstance();
// Get Scheduler Properties
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Scheduler名
String schedName = cfg.getStringProperty(PROP_SCHED_INSTANCE_NAME,
"QuartzScheduler");
//调度线程名
String threadName = cfg.getStringProperty(PROP_SCHED_THREAD_NAME,
schedName + "_QuartzSchedulerThread");
//实例的id,默认 NON_CLUSTERED
String schedInstId = cfg.getStringProperty(PROP_SCHED_INSTANCE_ID,
DEFAULT_INSTANCE_ID);
//实例ID生成策略
if (schedInstId.equals(AUTO_GENERATE_INSTANCE_ID)) {
autoId = true;
instanceIdGeneratorClass = cfg.getStringProperty(
PROP_SCHED_INSTANCE_ID_GENERATOR_CLASS,
"org.quartz.simpl.SimpleInstanceIdGenerator");
}
else if (schedInstId.equals(SYSTEM_PROPERTY_AS_INSTANCE_ID)) {
autoId = true;
instanceIdGeneratorClass =
"org.quartz.simpl.SystemPropertyInstanceIdGenerator";
}
//org.quartz.scheduler.userTransactionURL
userTXLocation = cfg.getStringProperty(PROP_SCHED_USER_TX_URL,
userTXLocation);
if (userTXLocation != null && userTXLocation.trim().length() == 0) {
userTXLocation = null;
}
//org.quartz.scheduler.classLoadHelper.class
classLoadHelperClass = cfg.getStringProperty(
PROP_SCHED_CLASS_LOAD_HELPER_CLASS,
"org.quartz.simpl.CascadingClassLoadHelper");
wrapJobInTx = cfg.getBooleanProperty(PROP_SCHED_WRAP_JOB_IN_USER_TX,
wrapJobInTx);
//org.quartz.scheduler.jobFactory.class
jobFactoryClass = cfg.getStringProperty(
PROP_SCHED_JOB_FACTORY_CLASS, null);
//org.quartz.scheduler.idleWaitTime
idleWaitTime = cfg.getLongProperty(PROP_SCHED_IDLE_WAIT_TIME,
idleWaitTime);
if(idleWaitTime > -1 && idleWaitTime < 1000) {
throw new SchedulerException("org.quartz.scheduler.idleWaitTime of less than 1000ms is not legal.");
}
//org.quartz.scheduler.dbFailureRetryInterval 数据库操作失败重试间隔,默认15秒
dbFailureRetry = cfg.getLongProperty(PROP_SCHED_DB_FAILURE_RETRY_INTERVAL, dbFailureRetry);
if (dbFailureRetry < 0) {
throw new SchedulerException(PROP_SCHED_DB_FAILURE_RETRY_INTERVAL + " of less than 0 ms is not legal.");
}
//是否生成守护线程
boolean makeSchedulerThreadDaemon =
cfg.getBooleanProperty(PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON);
//org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer 上下文加载器
boolean threadsInheritInitalizersClassLoader =
cfg.getBooleanProperty(PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD);
//org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow
long batchTimeWindow = cfg.getLongProperty(PROP_SCHED_BATCH_TIME_WINDOW, 0L);
//org.quartz.scheduler.batchTriggerAcquisitionMaxCount
int maxBatchSize = cfg.getIntProperty(PROP_SCHED_MAX_BATCH_SIZE, 1);
boolean interruptJobsOnShutdown = cfg.getBooleanProperty(PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN, false);
boolean interruptJobsOnShutdownWithWait = cfg.getBooleanProperty(PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN_WITH_WAIT, false);
/*
注:JMX(Java Management Extensions,即Java管理扩展)是Java平台上为应用程序、设备、系统等植入管理功能的框架。
JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
*/
boolean jmxExport = cfg.getBooleanProperty(PROP_SCHED_JMX_EXPORT);
String jmxObjectName = cfg.getStringProperty(PROP_SCHED_JMX_OBJECT_NAME);
boolean jmxProxy = cfg.getBooleanProperty(PROP_SCHED_JMX_PROXY);
String jmxProxyClass = cfg.getStringProperty(PROP_SCHED_JMX_PROXY_CLASS);
/* RMI:Java远程方法调用,即Java RMI(Java Remote Method Invocation)
是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。
远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。
*/
boolean rmiExport = cfg.getBooleanProperty(PROP_SCHED_RMI_EXPORT, false);
boolean rmiProxy = cfg.getBooleanProperty(PROP_SCHED_RMI_PROXY, false);
String rmiHost = cfg.getStringProperty(PROP_SCHED_RMI_HOST, "localhost");
int rmiPort = cfg.getIntProperty(PROP_SCHED_RMI_PORT, 1099);
int rmiServerPort = cfg.getIntProperty(PROP_SCHED_RMI_SERVER_PORT, -1);
String rmiCreateRegistry = cfg.getStringProperty(
PROP_SCHED_RMI_CREATE_REGISTRY,
QuartzSchedulerResources.CREATE_REGISTRY_NEVER);
String rmiBindName = cfg.getStringProperty(PROP_SCHED_RMI_BIND_NAME);
if (jmxProxy && rmiProxy) {
throw new SchedulerConfigException("Cannot proxy both RMI and JMX.");
}
boolean managementRESTServiceEnabled = cfg.getBooleanProperty(MANAGEMENT_REST_SERVICE_ENABLED, false);
String managementRESTServiceHostAndPort = cfg.getStringProperty(MANAGEMENT_REST_SERVICE_HOST_PORT, "0.0.0.0:9889");
Properties schedCtxtProps = cfg.getPropertyGroup(PROP_SCHED_CONTEXT_PREFIX, true);
以上代码就是解析properties中的配置,如果没有配置,获取默认值。
RMI和JMX有关远程调用的暂时不考虑。
2、获取JobFactory配置
JobFactory jobFactory = null;
if(jobFactoryClass != null) {
try {
jobFactory = (JobFactory) loadHelper.loadClass(jobFactoryClass).getConstructor()
.newInstance();
} catch (Exception e) {
throw new SchedulerConfigException(
"Unable to instantiate JobFactory class: "
+ e.getMessage(), e);
}
tProps = cfg.getPropertyGroup(PROP_SCHED_JOB_FACTORY_PREFIX, true);
try {
setBeanProps(jobFactory, tProps);
} catch (Exception e) {
initException = new SchedulerException("JobFactory class '"
+ jobFactoryClass + "' props could not be configured.", e);
throw initException;
}
}
解析配org.quartz.scheduler.jobFactory
将其下的属性都赋值给JobFactory
3、获取InstanceIdGenerator配置
InstanceIdGenerator instanceIdGenerator = null;
if(instanceIdGeneratorClass != null) {
try {
instanceIdGenerator = (InstanceIdGenerator) loadHelper.loadClass(instanceIdGeneratorClass).getConstructor()
.newInstance();
} catch (Exception e) {
throw new SchedulerConfigException(
"Unable to instantiate InstanceIdGenerator class: "
+ e.getMessage(), e);
}
tProps = cfg.getPropertyGroup(PROP_SCHED_INSTANCE_ID_GENERATOR_PREFIX, true);
try {
setBeanProps(instanceIdGenerator, tProps);
} catch (Exception e) {
initException = new SchedulerException("InstanceIdGenerator class '"
+ instanceIdGeneratorClass + "' props could not be configured.", e);
throw initException;
}
}
解析org.quartz.scheduler.instanceIdGenerator
下的属性,并赋值InstanceIdGenerator。
4、获取线程池配置
// Get ThreadPool Properties
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//从配置中获取线程池的类路径,如果没有的话就拿SimpleThreadPool
String tpClass = cfg.getStringProperty(PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());
if (tpClass == null) {
initException = new SchedulerException(
"ThreadPool class not specified. ");
throw initException;
}
//初始化主线程池SimpleThreadPool
try {
tp = (ThreadPool) loadHelper.loadClass(tpClass).getConstructor().newInstance();
} catch (Exception e) {
initException = new SchedulerException("ThreadPool class '"
+ tpClass + "' could not be instantiated.", e);
throw initException;
}
tProps = cfg.getPropertyGroup(PROP_THREAD_POOL_PREFIX, true);
try {
setBeanProps(tp, tProps);
} catch (Exception e) {
initException = new SchedulerException("ThreadPool class '"
+ tpClass + "' props could not be configured.", e);
throw initException;
}
获取org.quartz.threadPool.class
的配置,不配置默认SimpleThreadPool
。获取org.quartz.threadPool
下的配置,并将属性赋值给ThreadPool
.
5、获取存储类型配置
// Get JobStore Properties
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 获取任务存储中心配置,创建任务存储中心 默认使用RAMJobStore内存存储
String jsClass = cfg.getStringProperty(PROP_JOB_STORE_CLASS,
RAMJobStore.class.getName());
if (jsClass == null) {
initException = new SchedulerException(
"JobStore class not specified. ");
throw initException;
}
//初始化存储中心
try {
js = (JobStore) loadHelper.loadClass(jsClass).getConstructor().newInstance();
} catch (Exception e) {
initException = new SchedulerException("JobStore class '" + jsClass
+ "' could not be instantiated.", e);
throw initException;
}
SchedulerDetailsSetter.setDetails(js, schedName, schedInstId);
tProps = cfg.getPropertyGroup(PROP_JOB_STORE_PREFIX, true, new String[] {PROP_JOB_STORE_LOCK_HANDLER_PREFIX});
try {
setBeanProps(js, tProps);
} catch (Exception e) {
initException = new SchedulerException("JobStore class '" + jsClass
+ "' props could not be configured.", e);
throw initException;
}
if (js instanceof JobStoreSupport) {
// Install custom lock handler (Semaphore)
String lockHandlerClass = cfg.getStringProperty(PROP_JOB_STORE_LOCK_HANDLER_CLASS);
if (lockHandlerClass != null) {
try {
Semaphore lockHandler = (Semaphore)loadHelper.loadClass(lockHandlerClass).getConstructor().newInstance();
tProps = cfg.getPropertyGroup(PROP_JOB_STORE_LOCK_HANDLER_PREFIX, true);
// If this lock handler requires the table prefix, add it to its properties.
if (lockHandler instanceof TablePrefixAware) {
tProps.setProperty(
PROP_TABLE_PREFIX, ((JobStoreSupport)js).getTablePrefix());
tProps.setProperty(
PROP_SCHED_NAME, schedName);
}
try {
setBeanProps(lockHandler, tProps);
} catch (Exception e) {
initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass
+ "' props could not be configured.", e);
throw initException;
}
((JobStoreSupport)js).setLockHandler(lockHandler);
getLog().info("Using custom data access locking (synchronization): " + lockHandlerClass);
} catch (Exception e) {
initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass
+ "' could not be instantiated.", e);
throw initException;
}
}
}
根据org.quartz.jobStore.class
获取任务存储中心配置,默认使用RAMJobStore内存存储
如果配置为数据库存储JobStoreSupport
,再看是否配置自定义锁处理器
6、获取数据源配置
// Set up any DataSources
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
String[] dsNames = cfg.getPropertyGroups(PROP_DATASOURCE_PREFIX);
for (int i = 0; i < dsNames.length; i++) {
PropertiesParser pp = new PropertiesParser(cfg.getPropertyGroup(
PROP_DATASOURCE_PREFIX + "." + dsNames[i], true));
//自定义的数据库连接器
String cpClass = pp.getStringProperty(PROP_CONNECTION_PROVIDER_CLASS, null);
// custom connectionProvider...
if(cpClass != null) {
ConnectionProvider cp = null;
try {
cp = (ConnectionProvider) loadHelper.loadClass(cpClass).getConstructor().newInstance();
} catch (Exception e) {
initException = new SchedulerException("ConnectionProvider class '" + cpClass
+ "' could not be instantiated.", e);
throw initException;
}
try {
// remove the class name, so it isn't attempted to be set
pp.getUnderlyingProperties().remove(
PROP_CONNECTION_PROVIDER_CLASS);
//数据库连接池
if (cp instanceof PoolingConnectionProvider) {
populateProviderWithExtraProps((PoolingConnectionProvider)cp, pp.getUnderlyingProperties());
} else {
//其他非jdbc接口,例如:JND、Weblogic
setBeanProps(cp, pp.getUnderlyingProperties());
}
cp.initialize();
} catch (Exception e) {
initException = new SchedulerException("ConnectionProvider class '" + cpClass
+ "' props could not be configured.", e);
throw initException;
}
dbMgr = DBConnectionManager.getInstance();
dbMgr.addConnectionProvider(dsNames[i], cp);
} else {
String dsJndi = pp.getStringProperty(PROP_DATASOURCE_JNDI_URL, null);
if (dsJndi != null) {
boolean dsAlwaysLookup = pp.getBooleanProperty(
PROP_DATASOURCE_JNDI_ALWAYS_LOOKUP);
String dsJndiInitial = pp.getStringProperty(
PROP_DATASOURCE_JNDI_INITIAL);
String dsJndiProvider = pp.getStringProperty(
PROP_DATASOURCE_JNDI_PROVDER);
String dsJndiPrincipal = pp.getStringProperty(
PROP_DATASOURCE_JNDI_PRINCIPAL);
String dsJndiCredentials = pp.getStringProperty(
PROP_DATASOURCE_JNDI_CREDENTIALS);
Properties props = null;
if (null != dsJndiInitial || null != dsJndiProvider
|| null != dsJndiPrincipal || null != dsJndiCredentials) {
props = new Properties();
if (dsJndiInitial != null) {
props.put(PROP_DATASOURCE_JNDI_INITIAL,
dsJndiInitial);
}
if (dsJndiProvider != null) {
props.put(PROP_DATASOURCE_JNDI_PROVDER,
dsJndiProvider);
}
if (dsJndiPrincipal != null) {
props.put(PROP_DATASOURCE_JNDI_PRINCIPAL,
dsJndiPrincipal);
}
if (dsJndiCredentials != null) {
props.put(PROP_DATASOURCE_JNDI_CREDENTIALS,
dsJndiCredentials);
}
}
JNDIConnectionProvider cp = new JNDIConnectionProvider(dsJndi,
props, dsAlwaysLookup);
dbMgr = DBConnectionManager.getInstance();
dbMgr.addConnectionProvider(dsNames[i], cp);
} else {
String poolingProvider = pp.getStringProperty(PoolingConnectionProvider.POOLING_PROVIDER);
String dsDriver = pp.getStringProperty(PoolingConnectionProvider.DB_DRIVER);
String dsURL = pp.getStringProperty(PoolingConnectionProvider.DB_URL);
if (dsDriver == null) {
initException = new SchedulerException(
"Driver not specified for DataSource: "
+ dsNames[i]);
throw initException;
}
if (dsURL == null) {
initException = new SchedulerException(
"DB URL not specified for DataSource: "
+ dsNames[i]);
throw initException;
}
// we load even these "core" providers by class name in order to avoid a static dependency on
// the c3p0 and hikaricp libraries
if(poolingProvider != null && poolingProvider.equals(PoolingConnectionProvider.POOLING_PROVIDER_HIKARICP)) {
cpClass = "org.quartz.utils.HikariCpPoolingConnectionProvider";
}
else {
cpClass = "org.quartz.utils.C3p0PoolingConnectionProvider";
}
log.info("Using ConnectionProvider class '" + cpClass + "' for data source '" + dsNames[i] + "'");
try {
ConnectionProvider cp = null;
try {
Constructor<?> constructor = loadHelper.loadClass(cpClass).getConstructor(Properties.class);
cp = (ConnectionProvider) constructor.newInstance(pp.getUnderlyingProperties());
} catch (Exception e) {
initException = new SchedulerException("ConnectionProvider class '" + cpClass
+ "' could not be instantiated.", e);
throw initException;
}
dbMgr = DBConnectionManager.getInstance();
dbMgr.addConnectionProvider(dsNames[i], cp);
// Populate the underlying C3P0/HikariCP data source pool properties
populateProviderWithExtraProps((PoolingConnectionProvider)cp, pp.getUnderlyingProperties());
} catch (Exception sqle) {
initException = new SchedulerException(
"Could not initialize DataSource: " + dsNames[i],
sqle);
throw initException;
}
}
}
}
获取org.quartz.dataSource
数据源配置,可以支持配置多个数据源。
可配置自定义的数据库连接池,默认支持C3P0C3p0PoolingConnectionProvider
和HikaricpHikariCpPoolingConnectionProvider
两种数据连接池。
还可以配置非JDBC的连接器:JNDIConnectionProvider
、WeblogicConnectionProvider
等等。
如果没有配置自定义的数据库连接器:先查询是否有JNDI相关配置,如果有,则加载JNDI相关数据源。如果没有JNDI相关配置,直接加载数据源配置,内置数据库连接器为C3p0PoolingConnectionProvider
。
7、获取调度器插件配置
// Set up any SchedulerPlugins
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//注册插件
String[] pluginNames = cfg.getPropertyGroups(PROP_PLUGIN_PREFIX);
SchedulerPlugin[] plugins = new SchedulerPlugin[pluginNames.length];
for (int i = 0; i < pluginNames.length; i++) {
Properties pp = cfg.getPropertyGroup(PROP_PLUGIN_PREFIX + "."
+ pluginNames[i], true);
String plugInClass = pp.getProperty(PROP_PLUGIN_CLASS, null);
if (plugInClass == null) {
initException = new SchedulerException(
"SchedulerPlugin class not specified for plugin '"
+ pluginNames[i] + "'");
throw initException;
}
SchedulerPlugin plugin = null;
try {
plugin = (SchedulerPlugin)
loadHelper.loadClass(plugInClass).newInstance();
} catch (Exception e) {
initException = new SchedulerException(
"SchedulerPlugin class '" + plugInClass
+ "' could not be instantiated.", e);
throw initException;
}
try {
setBeanProps(plugin, pp);
} catch (Exception e) {
initException = new SchedulerException(
"JobStore SchedulerPlugin '" + plugInClass
+ "' props could not be configured.", e);
throw initException;
}
plugins[i] = plugin;
}
根据org.quartz.plugin
获取插件配置,可以注册多个插件,org.quartz.plugin.XXXX.class
获取插件的类路径。
8、获取任务监听器配置
// Set up any JobListeners
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 注册JOB监听器
Class<?>[] strArg = new Class[] { String.class };
String[] jobListenerNames = cfg.getPropertyGroups(PROP_JOB_LISTENER_PREFIX);
JobListener[] jobListeners = new JobListener[jobListenerNames.length];
for (int i = 0; i < jobListenerNames.length; i++) {
Properties lp = cfg.getPropertyGroup(PROP_JOB_LISTENER_PREFIX + "."
+ jobListenerNames[i], true);
String listenerClass = lp.getProperty(PROP_LISTENER_CLASS, null);
if (listenerClass == null) {
initException = new SchedulerException(
"JobListener class not specified for listener '"
+ jobListenerNames[i] + "'");
throw initException;
}
JobListener listener = null;
try {
listener = (JobListener)
loadHelper.loadClass(listenerClass).getConstructor().newInstance();
} catch (Exception e) {
initException = new SchedulerException(
"JobListener class '" + listenerClass
+ "' could not be instantiated.", e);
throw initException;
}
try {
Method nameSetter = null;
try {
nameSetter = listener.getClass().getMethod("setName", strArg);
}
catch(NoSuchMethodException ignore) {
/* do nothing */
}
if(nameSetter != null) {
nameSetter.invoke(listener, new Object[] {jobListenerNames[i] } );
}
setBeanProps(listener, lp);
} catch (Exception e) {
initException = new SchedulerException(
"JobListener '" + listenerClass
+ "' props could not be configured.", e);
throw initException;
}
jobListeners[i] = listener;
}
根据org.quartz.jobListener
获取监听器配置,类似插件配置.
9、获取触发器监听器配置
// Set up any TriggerListeners
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 注册Trigger监听器
String[] triggerListenerNames = cfg.getPropertyGroups(PROP_TRIGGER_LISTENER_PREFIX);
TriggerListener[] triggerListeners = new TriggerListener[triggerListenerNames.length];
for (int i = 0; i < triggerListenerNames.length; i++) {
Properties lp = cfg.getPropertyGroup(PROP_TRIGGER_LISTENER_PREFIX + "."
+ triggerListenerNames[i], true);
String listenerClass = lp.getProperty(PROP_LISTENER_CLASS, null);
if (listenerClass == null) {
initException = new SchedulerException(
"TriggerListener class not specified for listener '"
+ triggerListenerNames[i] + "'");
throw initException;
}
TriggerListener listener = null;
try {
listener = (TriggerListener)
loadHelper.loadClass(listenerClass).newInstance();
} catch (Exception e) {
initException = new SchedulerException(
"TriggerListener class '" + listenerClass
+ "' could not be instantiated.", e);
throw initException;
}
try {
Method nameSetter = null;
try {
nameSetter = listener.getClass().getMethod("setName", strArg);
}
catch(NoSuchMethodException ignore) { /* do nothing */ }
if(nameSetter != null) {
nameSetter.invoke(listener, new Object[] {triggerListenerNames[i] } );
}
setBeanProps(listener, lp);
} catch (Exception e) {
initException = new SchedulerException(
"TriggerListener '" + listenerClass
+ "' props could not be configured.", e);
throw initException;
}
triggerListeners[i] = listener;
}
根据org.quartz.triggerListener
获取Trigger监听器配置,其他类似插件配置。
10、获取执行线程配置
// Get ThreadExecutor Properties
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
String threadExecutorClass = cfg.getStringProperty(PROP_THREAD_EXECUTOR_CLASS);
if (threadExecutorClass != null) {
tProps = cfg.getPropertyGroup(PROP_THREAD_EXECUTOR, true);
try {
threadExecutor = (ThreadExecutor) loadHelper.loadClass(threadExecutorClass).getConstructor().newInstance();
log.info("Using custom implementation for ThreadExecutor: " + threadExecutorClass);
setBeanProps(threadExecutor, tProps);
} catch (Exception e) {
initException = new SchedulerException(
"ThreadExecutor class '" + threadExecutorClass + "' could not be instantiated.", e);
throw initException;
}
} else {
log.info("Using default implementation for ThreadExecutor 使用 ThreadExecutor 的默认实现");
threadExecutor = new DefaultThreadExecutor();
}
根据org.quartz.threadExecutor.class
获取执行线程的类路径,如果没有配置,就使用ThreadExecutor
的默认实现DefaultThreadExecutor
。默认的执行器就一个方法,就是启动一个线程。代码如下:
public class DefaultThreadExecutor implements ThreadExecutor {
public void initialize() {
}
public void execute(Thread thread) {
thread.start();
}
}
至此,所有的配置都获取完了,开始正式的调度了 Fire everything up
Fire everything up
1、创建JobRunShellFactory
JobRunShellFactory jrsf = null; // Create correct run-shell factory...
if (userTXLocation != null) {
UserTransactionHelper.setUserTxLocation(userTXLocation);
}
if (wrapJobInTx) { //fasle
jrsf = new JTAJobRunShellFactory();
} else {
jrsf = new JTAAnnotationAwareJobRunShellFactory();
}
从前面获取的org.quartz.scheduler.userTransactionURL
配置事物的地址
从org.quartz.scheduler.wrapJobExecutionInUserTransaction
获取数据判断是否将任务包装在事物中。
注意JTAJobRunShellFactory
和JTAAnnotationAwareJobRunShellFactory
这两个的区别:
JTAAnnotationAwareJobRunShellFactory
如果任务的类上存在注解ExecuteInJTATransaction
,就新增一个带超时时间的JTAJobRunShell
,否则就新建一个普通的JTAJobRunShell
。
public JobRunShell createJobRunShell(TriggerFiredBundle bundle)
throws SchedulerException {
ExecuteInJTATransaction jtaAnnotation = ClassUtils.getAnnotation(bundle.getJobDetail().getJobClass(), ExecuteInJTATransaction.class);
if(jtaAnnotation == null)
return new JobRunShell(scheduler, bundle);
else {
int timeout = jtaAnnotation.timeout();
if (timeout >= 0) {
return new JTAJobRunShell(scheduler, bundle, timeout);
} else {
return new JTAJobRunShell(scheduler, bundle);
}
}
}
2、设置任务调度唯一标识
//设置任务调度器唯一标识符
if (autoId) {
try {
schedInstId = DEFAULT_INSTANCE_ID;
if (js.isClustered()) {
//如果JobStore是集群,必须要有调度器唯一标识符生成器
schedInstId = instanceIdGenerator.generateInstanceId();
}
} catch (Exception e) {
getLog().error("Couldn't generate instance Id!", e);
throw new IllegalStateException("Cannot run without an instance id.");
}
}
3、数据库存储类型为JobStoreSupport
的处理
if (js instanceof JobStoreSupport) {
JobStoreSupport jjs = (JobStoreSupport)js;
jjs.setDbRetryInterval(dbFailureRetry);
//线程是否继承Initalizers类加载器
if(threadsInheritInitalizersClassLoader)
jjs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader);
jjs.setThreadExecutor(threadExecutor);
}
4、创建调度器资源类
//开始创建调度器资源类
QuartzSchedulerResources rsrcs = new QuartzSchedulerResources();
rsrcs.setName(schedName);
rsrcs.setThreadName(threadName);
rsrcs.setInstanceId(schedInstId);
rsrcs.setJobRunShellFactory(jrsf);
rsrcs.setMakeSchedulerThreadDaemon(makeSchedulerThreadDaemon);
rsrcs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader);
rsrcs.setBatchTimeWindow(batchTimeWindow);
rsrcs.setMaxBatchSize(maxBatchSize);
rsrcs.setInterruptJobsOnShutdown(interruptJobsOnShutdown);
rsrcs.setInterruptJobsOnShutdownWithWait(interruptJobsOnShutdownWithWait);
rsrcs.setJMXExport(jmxExport);
rsrcs.setJMXObjectName(jmxObjectName);
if (managementRESTServiceEnabled) { //false
ManagementRESTServiceConfiguration managementRESTServiceConfiguration = new ManagementRESTServiceConfiguration();
managementRESTServiceConfiguration.setBind(managementRESTServiceHostAndPort);
managementRESTServiceConfiguration.setEnabled(managementRESTServiceEnabled);
rsrcs.setManagementRESTServiceConfiguration(managementRESTServiceConfiguration);
}
if (rmiExport) { //false
rsrcs.setRMIRegistryHost(rmiHost);
rsrcs.setRMIRegistryPort(rmiPort);
rsrcs.setRMIServerPort(rmiServerPort);
rsrcs.setRMICreateRegistryStrategy(rmiCreateRegistry);
rsrcs.setRMIBindName(rmiBindName);
}
// ThreadPool tp 是线程池,这里是对setInstanceName,setInstanceId赋值
SchedulerDetailsSetter.setDetails(tp, schedName, schedInstId);
// 在调度器资源类中放入线程执行器
rsrcs.setThreadExecutor(threadExecutor);
//线程执行器初始化,默认空方法
threadExecutor.initialize();
//设置线程池
rsrcs.setThreadPool(tp);
if(tp instanceof SimpleThreadPool) {
if(threadsInheritInitalizersClassLoader)
((SimpleThreadPool)tp).setThreadsInheritContextClassLoaderOfInitializingThread(threadsInheritInitalizersClassLoader);
}
/**
* 工作线程的初始化(重要)
*/
tp.initialize();
tpInited = true;
rsrcs.setJobStore(js);
// add plugins
for (int i = 0; i < plugins.length; i++) {
rsrcs.addSchedulerPlugin(plugins[i]);
}
QuartzSchedulerResources
类似上下文存储的数据,存储需要调度的参数。
这一段最重要的是工作线程的初始化,这里以ThreadPool
的默认实现SimpleThreadPool
说明:
public void initialize() throws SchedulerConfigException {
if(workers != null && workers.size() > 0) // already initialized...
return;
if (count <= 0) {
throw new SchedulerConfigException(
"Thread count must be > 0");
}
if (prio <= 0 || prio > 9) {
throw new SchedulerConfigException(
"Thread priority must be > 0 and <= 9");
}
if(isThreadsInheritGroupOfInitializingThread()) {
threadGroup = Thread.currentThread().getThreadGroup();
} else {
//查询threadGroup树形找到Main线程ThreadGroup
// follow the threadGroup tree to the root thread group.
threadGroup = Thread.currentThread().getThreadGroup();
ThreadGroup parent = threadGroup;
while ( !parent.getName().equals("main") ) {
threadGroup = parent;
parent = threadGroup.getParent();
}
threadGroup = new ThreadGroup(parent, schedulerInstanceName + "-SimpleThreadPool");
if (isMakeThreadsDaemons()) {
threadGroup.setDaemon(true);
}
}
if (isThreadsInheritContextClassLoaderOfInitializingThread()) {
getLog().info(
"Job execution threads will use class loader of thread: "
+ Thread.currentThread().getName());
}
// create the worker threads and start them
Iterator<WorkerThread> workerThreads = createWorkerThreads(count).iterator();
while(workerThreads.hasNext()) {
WorkerThread wt = workerThreads.next();
wt.start();
availWorkers.add(wt);
}
}
前半段的部分是初始化一个ThreadGroup
,本来Java是不推荐ThreadGroup
的,但是这里通过ThreadGroup
可以对工作线程进行分组,可能出于管理线程的角度。这个方法最核心的就是createWorkerThreads
方法,先看这个方法:
protected List<WorkerThread> createWorkerThreads(int createCount) {
workers = new LinkedList<WorkerThread>();
for (int i = 1; i<= createCount; ++i) {
String threadPrefix = getThreadNamePrefix();
if (threadPrefix == null) {
threadPrefix = schedulerInstanceName + "_Worker";
}
WorkerThread wt = new WorkerThread(this, threadGroup,
threadPrefix + "-" + i,
getThreadPriority(),
isMakeThreadsDaemons());
//没有地方调用设置的方法,一直为true
if (isThreadsInheritContextClassLoaderOfInitializingThread()) {
wt.setContextClassLoader(Thread.currentThread()
.getContextClassLoader());
}
workers.add(wt);
}
return workers;
}
这里传入的createCount
是系统的逻辑核数。这里的for
循环,创建核数个线程WorkerThread
,尽可能利用系统资源,并将新建的线程保存在workers
全局变量中。
createWorkerThreads
方法结束后,遍历新创建的线程集合workers
,全部启动,并存放在availWorkers
中,表示可用线程。
注意这里WorkerThread
的构造函数,有5个参数,这里的runabe为null。
/**
* <p>
* Create a worker thread and start it. Waiting for the next Runnable,
* executing it, and waiting for the next Runnable, until the shutdown
* flag is set.
* </p>
*/
WorkerThread(SimpleThreadPool tp, ThreadGroup threadGroup, String name,
int prio, boolean isDaemon) {
this(tp, threadGroup, name, prio, isDaemon, null);
}
/**
* <p>
* Create a worker thread, start it, execute the runnable and terminate
* the thread (one time execution).
* </p>
*/
WorkerThread(SimpleThreadPool tp, ThreadGroup threadGroup, String name,
int prio, boolean isDaemon, Runnable runnable) {
super(threadGroup, name);
this.tp = tp;
this.runnable = runnable;
if(runnable != null)
runOnce = true;
setPriority(prio);
setDaemon(isDaemon);
}
5、WorkerThread
工作线程调度
上面说到workers
线程启动了,现在我们看看WorkerThread
线程里的逻辑:
WorkerThread
是SimpleThreadPool
这个类中的内部类
/**
* <p>
* Loop, executing targets as they are received.
* 循环,在收到目标时执行它们。
* </p>
*/
@Override
public void run() {
boolean ran = false;
while (run.get()) {
try {
synchronized(lock) {
while (runnable == null && run.get()) {
lock.wait(500);
}
if (runnable != null) {
ran = true;
//启动任务
runnable.run();
}
}
} catch (InterruptedException unblock) {
// do nothing (loop will terminate if shutdown() was called
try {
getLog().error("Worker thread was interrupt()'ed.", unblock);
} catch(Exception e) {
// ignore to help with a tomcat glitch
}
} catch (Throwable exceptionInRunnable) {
try {
getLog().error("Error while executing the Runnable: ",
exceptionInRunnable);
} catch(Exception e) {
// ignore to help with a tomcat glitch
}
} finally {
synchronized(lock) {
runnable = null;
}
// repair the thread in case the runnable mucked it up...
if(getPriority() != tp.getThreadPriority()) {
setPriority(tp.getThreadPriority());
}
if (runOnce) {
run.set(false);
clearFromBusyWorkersList(this);
} else if(ran) {
ran = false;
//线程重新加入可用线程
makeAvailable(this);
}
}
}
//if (log.isDebugEnabled())
try {
getLog().debug("WorkerThread is shut down.");
} catch(Exception e) {
// ignore to help with a tomcat glitch
}
}
首先,run.get()
默认是原子类true,如果没有shutdown的话,会一直处于循环中。
进入同步代码块后,上面一步说到这里的runnable为null。如果一直没有线程进来的话,这里就会一直死循环,等待500ms继续循环。如果有线程进来的话,直接启动线程。目前生成Schedule阶段,不会存在有业务线程的情况,这个方法的后续,我们之后再说。
6、实例化QuartzScheduler
/**
* 实例化 其中包含运行的线程
*/
qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
qsInited = true;
// Create Scheduler ref...// 关键点,创建出Scheduler,默认是StdScheduler
Scheduler scheduler = instantiate(rsrcs, qs);
// set job factory if specified
if(jobFactory != null) {
qs.setJobFactory(jobFactory);
}
这段代码直接创建出了QuartzScheduler
,这个也是一个非常重要的类。它将之前创建的资源类QuartzSchedulerResources
传入。
接下来看一下QuartzScheduler
的构造方法:
public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)
throws SchedulerException {
this.resources = resources;
if (resources.getJobStore() instanceof JobListener) {
addInternalJobListener((JobListener)resources.getJobStore());
}
/**
* 创建调度线程 并执行
*/
this.schedThread = new QuartzSchedulerThread(this, resources);
ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
schedThreadExecutor.execute(this.schedThread);
if (idleWaitTime > 0) {
this.schedThread.setIdleWaitTime(idleWaitTime);
}
jobMgr = new ExecutingJobsManager();
addInternalJobListener(jobMgr);
errLogger = new ErrorLogger();
addInternalSchedulerListener(errLogger);
signaler = new SchedulerSignalerImpl(this, this.schedThread);
getLog().info("Quartz Scheduler v." + getVersion() + " created.");
}
第一段判断JobStore如果是JobListener,就将JobStore加入监听器中,但是我看了几种默认的JobStore
实现,包含RAMJobStore
、JobStoreSupport
、JobStoreTX
、JobStoreCMT
,其中都没有包含监听器接口的,所以这里先不管。
第二段是重点,实例化了一个QuartzSchedulerThread
,并由我们前面创建的线程执行器DefaultThreadExecutor
执行的。
同时:这里new了一个信号调度器SchedulerSignalerImpl
,这个后面会用到。
先看看QuartzSchedulerThread
的构造函数:
QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs, boolean setDaemon, int threadPrio) {
super(qs.getSchedulerThreadGroup(), qsRsrcs.getThreadName());
this.qs = qs;
this.qsRsrcs = qsRsrcs;
this.setDaemon(setDaemon);
if(qsRsrcs.isThreadsInheritInitializersClassLoadContext()) {
log.info("QuartzSchedulerThread Inheriting ContextClassLoader of thread: " + Thread.currentThread().getName());
this.setContextClassLoader(Thread.currentThread().getContextClassLoader());
}
this.setPriority(threadPrio);
// start the underlying thread, but put this object into the 'paused'
// state
// so processing doesn't start yet...
//暂停
paused = true;
//主线程停止标志
halted = new AtomicBoolean(false);
}
这里将QuartzScheduler
和QuartzSchedulerResources
存入,然后设置paused为true,设置halted为false。这两个变量是控制主线程暂停和停止的标志,paused为true表示,目前启动阶段暂时暂停主线程,halted为false表示主线程未停止,暂停可以重启,但是停止就不能重启了。
7、QuartzSchedulerThread
主线程调度
进入QuartzSchedulerThread线程的run方法:
/*
//暂停
paused = true;
//主线程停止标志
halted = new AtomicBoolean(false);
*/
while (!halted.get()) {
while (paused && !halted.get()) {
try {
// wait until togglePause(false) is called...
//等到 togglePause(false) 被调用...
log.info("QuartzSchedulerThread 等到 togglePause(false) 被调用..." + LocalDateTime.now());
sigLock.wait(1000L);
} catch (InterruptedException ignore) {
}
// reset failure counter when paused, so that we don't
// wait again after unpausing
acquiresFailed = 0;
}
// 省略不执行的方法。。。。
}
} // while (!halted)
// drop references to scheduler stuff to aid garbage collection...
qs = null;
qsRsrcs = null;
如果主线程不停止,while会一直执行,不停的调度。
进入同步代码块,获取sigLock锁,前面说到paused=true,halted=false,所以这里会进入while循环中,等待1s后继续判断。这里的注释写到:等到 togglePause(false) 被调用…
抢先体验一下这个方法都在干啥:
void togglePause(boolean pause) {
synchronized (sigLock) {
paused = pause;
if (paused) {
signalSchedulingChange(0);
} else {
sigLock.notifyAll();
}
}
}
这里传入false的话,暂停开关变成false,主线程就可以继续调度了。
这里一定要加锁,防止多线程同时调度此方法。
最后唤醒所有等待sigLock锁的线程,进行线程抢占调度。
8、注册自定义插件、监听器
再回到第6点,实例化QuartzScheduler
后,开始注册自定义插件、监听器
// Initialize plugins now that we have a Scheduler instance.
for (int i = 0; i < plugins.length; i++) {
plugins[i].initialize(pluginNames[i], scheduler, loadHelper);
}
// add listeners
for (JobListener jobListener : jobListeners) {
qs.getListenerManager().addJobListener(jobListener, EverythingMatcher.allJobs());
}
for (TriggerListener triggerListener : triggerListeners) {
qs.getListenerManager().addTriggerListener(triggerListener, EverythingMatcher.allTriggers());
}
// set scheduler context data...
for(Object key: schedCtxtProps.keySet()) {
String val = schedCtxtProps.getProperty((String) key);
scheduler.getContext().put((String)key, val);
}
9、初始化JobStore
// fire up job store, and runshell factory
// 开始初始化JobStore
js.setInstanceId(schedInstId);
js.setInstanceName(schedName);
js.setThreadPoolSize(tp.getPoolSize());
//初始化锁 这里前面创建qs的最后一步设置的
js.initialize(loadHelper, qs.getSchedulerSignaler());
//其实就是在jrsf注入scheduler变量
jrsf.initialize(scheduler);
//这里做了远程绑定,如果没有的话会直接跳过(rmi,jmx)
qs.initialize();
getLog().info(
"Quartz scheduler '" + scheduler.getSchedulerName()
+ "' initialized from " + propSrc);
getLog().info("Quartz scheduler version: " + qs.getVersion());
// prevents the repository from being garbage collected 防止存储库被垃圾收集
qs.addNoGCObject(schedRep);
// prevents the db manager from being garbage collected 防止数据库管理器被垃圾收集
if (dbMgr != null) {
qs.addNoGCObject(dbMgr);
}
//调用程序库SchedulerRepository中新添加scheduler
schedRep.bind(scheduler);
return scheduler;
先看一下代码中的初始化锁:
在RAMJobStore
中直接初始化了signaler
在``中初始化了loadHelper和signaler
如果用户没有显示在配置文件中指定锁处理器,如果是集群,就上数据库锁,查询锁的sql如下:
SELECT * FROM {0}LOCKS WITH (UPDLOCK,ROWLOCK) WHERE " + COL_SCHEDULER_NAME + " = {1} AND LOCK_NAME = ?
并将sql封装成StdRowLockSemaphore
:内部基于数据库的锁处理程序,用于提供线程资源锁定,以防止资源同时被多个线程更改。
如果没有上数据库锁,默认封装一个SimpleSemaphore
:内部内存锁处理程序,用于提供线程资源锁定,以保护资源不被多个线程同时更改。
结束语
到此为止,scheduler就被创建出来了。