Source code analysis of QuartzScheduler

Posted by Sillysoft on Fri, 10 Dec 2021 01:18:59 +0100

Logger log = LoggerFactory.getLogger(SimpleExample.class);

log.info("------- Initializing ----------------------");

// First we must get a reference to a scheduler
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();

log.info("------- Initialization Complete -----------");

// computer a time that is on the next round minute
Date runTime = evenMinuteDate(new Date());

log.info("------- Scheduling Job -------------------");

// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();

// Trigger the job to run on the next round minute
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();

// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
log.info(job.getKey() + " will run at: " + runTime);

// Start up the scheduler (nothing can actually run until the
// scheduler has been started)
sched.start();

log.info("------- Started Scheduler -----------------");

// wait long enough so that the scheduler as an opportunity to
// run the job!
log.info("------- Waiting 65 seconds... -------------");
try {
// wait 65 seconds to show job
Thread.sleep(65L * 1000L);
// executing...
} catch (Exception e) {
//
}

// shut down the scheduler
log.info("------- Shutting Down ---------------------");
sched.shutdown(true);
log.info("------- Shutdown Complete -----------------");

Analyze the above source code:

SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();

StdSchedulerFactory is an implementation class of SchedulerFactory. Its main purpose is to create an instance of QuartzScheduler with Properties.
By default, quartz Properties is loaded from the current working directory. If it fails, it will be loaded in quartz. org/quartz under the package directory If you want to customize a file, you need to set org quartz. The properties system property points to that file.
Before calling the getScheduler() method, you need to call the initialize(xx) method to initialize the factory.
You can see the sample properties file
Jobstore, ThreadPool instance or other spi classes will be created, and any other specified properties will be equivalent to calling the set method. For example, the property org. Is set in the properties configuration file quartz. jobStore. Myprop = 10. After the jobstore instance session, the method setMyProp() will be called. In method setMyProp() The type is converted to the original type before it will be called. One property can point to the value of another property, such as use case: $@ org quartz. scheduler. InstanceName points to the same value

sf.getScheduler();
All the codes are as follows:
public Scheduler getScheduler() throws SchedulerException {
if (cfg == null) {
initialize(); // initialization
}

    SchedulerRepository schedRep = SchedulerRepository.getInstance();

    Scheduler sched = schedRep.lookup(getSchedulerName());

    if (sched != null) {
        if (sched.isShutdown()) {
            schedRep.remove(getSchedulerName());
        } else {
            return sched;
        }
    }

    sched = instantiate(); //instantiation 

    return sched;
}

SchedulerRepository is the storage warehouse of Scheduler. It adopts simple profit mode and maintains a map. Schedulers = new HashMap < string, Scheduler > ();
Why is SchedulerRepository introduced?
The purpose is not to maintain the map separately, and it is a simple interest Scheduler. The most important thing is to encapsulate a map, which only exposes a few method operations, such as lookup, to avoid exposing the map and reduce the occurrence of user errors.

Parse initialize() initialization method:
Initialize SchedulerFactory with the properties file and overridden system properties.
The system properties will overwrite the properties in the file.
By default, quartz Properties is loaded from the current working directory. If it fails, it will be loaded in quartz. org/quartz under the package directory If you want to customize a file, you need to set org quartz. The properties system property points to that file.

Initialization, write the properties property in the file into props, and then encapsulate props into the PropertiesParser class.
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 {
        //File exists
        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;
            }
            //The specified file does not exist
        } 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 {
            //The system attribute is not specified. The default file is used
            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, getLog()));
}

Analyze the instantiate() instantiation method:
This code is too lengthy and looks a little unprofessional. Let's go one by one.

private Scheduler instantiate() throws SchedulerException {
if (cfg == null) {
initialize();
}

    if (initException != null) {
        throw initException;
    }

    JobStore js = null;
    //Thread pool
    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      = SchedulerRepository.getInstance();

    // Get Scheduler Properties
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //Task scheduling name
    String schedName = cfg.getStringProperty(PROP_SCHED_INSTANCE_NAME,
            "QuartzScheduler");
    //Thread name
    String threadName = cfg.getStringProperty(PROP_SCHED_THREAD_NAME,
            schedName + "_QuartzSchedulerThread");
    //Task scheduling instance id
    String schedInstId = cfg.getStringProperty(PROP_SCHED_INSTANCE_ID,
            DEFAULT_INSTANCE_ID);
    //schedInstId can be AUTO, and you will get the computer name, SYS_PROP needs to set attribute org quartz. scheduler. Instanceid is not read from the file, but the read system variable
    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";
    }

    userTXLocation = cfg.getStringProperty(PROP_SCHED_USER_TX_URL,
            userTXLocation);
    if (userTXLocation != null && userTXLocation.trim().length() == 0) {
        userTXLocation = null;
    }

    classLoadHelperClass = cfg.getStringProperty(
            PROP_SCHED_CLASS_LOAD_HELPER_CLASS,
            "org.quartz.simpl.CascadingClassLoadHelper");
    wrapJobInTx = cfg.getBooleanProperty(PROP_SCHED_WRAP_JOB_IN_USER_TX,
            wrapJobInTx);

    jobFactoryClass = cfg.getStringProperty(
            PROP_SCHED_JOB_FACTORY_CLASS, null);

    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.");
    }
    
    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);

    boolean threadsInheritInitalizersClassLoader =
        cfg.getBooleanProperty(PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD);

    long batchTimeWindow = cfg.getLongProperty(PROP_SCHED_BATCH_TIME_WINDOW, 0L);
    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);

    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);

    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);
    //jmx rmi cannot be activated at the same time
    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);

    //First stage return 
    // If Proxying to remote scheduler, short-circuit here...
    // ~~~~~~~~~~~~~~~~~~
    //When rmi exposure is selected, you can only request the address of the real scheduling machine to request scheduling, instead of the local machine becoming a scheduling task server. Similarly, the local quartz is just a quartz client to request the real scheduling task machine and return directly.
    if (rmiProxy) {

        if (autoId) {
            schedInstId = DEFAULT_INSTANCE_ID;
        }

        String uid = (rmiBindName == null) ? QuartzSchedulerResources.getUniqueIdentifier(
                schedName, schedInstId) : rmiBindName;

        RemoteScheduler remoteScheduler = new RemoteScheduler(uid, rmiHost, rmiPort);

        schedRep.bind(remoteScheduler);

        return remoteScheduler;
    }




    // Create class load helper
    ClassLoadHelper loadHelper = null;
    try {
        loadHelper = (ClassLoadHelper) loadClass(classLoadHelperClass)
                .newInstance();
    } catch (Exception e) {
        throw new SchedulerConfigException(
                "Unable to instantiate class load helper class: "
                        + e.getMessage(), e);
    }
    loadHelper.initialize();


    //The second stage returns the rmi remote procedure call similar to the above, except that the protocol is replaced by jmx remote procedure call
    //Determines whether to connect to the remote jmx, which means that it is only used as a quartz client. However, you need to provide your own implementation to realize communication.
    // If Proxying to remote JMX scheduler, short-circuit here...
    // ~~~~~~~~~~~~~~~~~~
    if (jmxProxy) {
        if (autoId) {
            schedInstId = DEFAULT_INSTANCE_ID;
        }

        if (jmxProxyClass == null) {
            throw new SchedulerConfigException("No JMX Proxy Scheduler class provided");
        }

        RemoteMBeanScheduler jmxScheduler = null;
        try {
            jmxScheduler = (RemoteMBeanScheduler)loadHelper.loadClass(jmxProxyClass)
                    .newInstance();
        } catch (Exception e) {
            throw new SchedulerConfigException(
                    "Unable to instantiate RemoteMBeanScheduler class.", e);
        }

        if (jmxObjectName == null) {
            jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId);
        }

        jmxScheduler.setSchedulerObjectName(jmxObjectName);

        tProps = cfg.getPropertyGroup(PROP_SCHED_JMX_PROXY, true);
        try {
            setBeanProps(jmxScheduler, tProps);
        } catch (Exception e) {
            initException = new SchedulerException("RemoteMBeanScheduler class '"
                    + jmxProxyClass + "' props could not be configured.", e);
            throw initException;
        }

        jmxScheduler.initialize();

        schedRep.bind(jmxScheduler);

        return jmxScheduler;
    }

    //The default value of the job factory is null. You need to provide your own implementation
    JobFactory jobFactory = null;
    if(jobFactoryClass != null) {
        try {
            jobFactory = (JobFactory) loadHelper.loadClass(jobFactoryClass)
                    .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;
        }
    }
    //Task id generator see org quartz. scheduler. The instanceid attribute value determines the class value. If AUTO, it is SimpleInstanceIdGenerator; if SYS_PROP is SystemPropertyInstanceIdGenerator
    InstanceIdGenerator instanceIdGenerator = null;
    if(instanceIdGeneratorClass != null) {
        try {
            instanceIdGenerator = (InstanceIdGenerator) loadHelper.loadClass(instanceIdGeneratorClass)
                .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;
        }
    }
    //Get the thread pool attribute. The default thread pool is SimpleThreadPool, which can be set by setting org quartz. threadPool. Name = 1 set the thread pool name to 1, and other properties are also set in this way. View class instances with specific property settings.
    // Get ThreadPool Properties
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    String tpClass = cfg.getStringProperty(PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());

    if (tpClass == null) {
        initException = new SchedulerException(
                "ThreadPool class not specified. ");
        throw initException;
    }

    try {
        tp = (ThreadPool) loadHelper.loadClass(tpClass).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;
    }
    //Get the properties of jobstore. RAMJobStore instance is used by default
    // Get JobStore Properties
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    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).newInstance();
    } catch (Exception e) {
        initException = new SchedulerException("JobStore class '" + jsClass
                + "' could not be instantiated.", e);
        throw initException;
    }
    //Set the name and id of the jobstore
    SchedulerDetailsSetter.setDetails(js, schedName, schedInstId);

    //Set other properties of the jobstore
    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;
    }
    //Set lockhandler for custom jobstore
    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).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;
            }
        }
    } 
    //To set up multiple database information, you can set org. Org quartz. dataSource. db1= org. quartz. dataSource. DB2 = these are two databases
    // 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);

        //Set the connectionprovider of the corresponding data source with the attribute org quartz. dataSource. db1. connectionProvider. class=
        // custom connectionProvider...
        if(cpClass != null) {
            ConnectionProvider cp = null;
            try {
                //Instantiate connectionProvider
                cp = (ConnectionProvider) loadHelper.loadClass(cpClass).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 it is an instance of PoolingConnectionProvider,
                if (cp instanceof PoolingConnectionProvider) {
                    populateProviderWithExtraProps((PoolingConnectionProvider)cp, pp.getUnderlyingProperties());
                } else {
                    //Set the connectionprovider property
                    setBeanProps(cp, pp.getUnderlyingProperties());
                }
                //Initialize connectionProvider
                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 {
            //If there is no connectionprovider setting, set jndi
            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 {
                //Set the connection provider common to all data sources
                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
                //Set the shared ConnectionProvider according to the provider category. If none is set, the default is c3p0 ConnectionProvider
                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;
                    }
                    //Put all connection providers into dbconnection manager for management
                    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;
                }
            }

        }

    }
    //Set task scheduling plug-in org quartz. plugin. Plugin1 = multiple plugins can be set in this way, org quartz. plugin. plugin2. Class =, the SchedulerPlugin interface needs to be implemented, but the settings have not been applied to
    // 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;
    }
    //Set up JobListeners, org quartz. JobListener. Listen1 =, implement JobListener, call setName to set the name. Setting other properties is just to set joblist, which is not applied
    // Set up any JobListeners
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    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).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;
    }
    //Install org quartz. Triggerlistener, setting multiple listeners, org quartz. triggerListener. Listen1 =, call the setName method to set the name and set other properties from the configuration file,
    // Set up any TriggerListeners
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    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;
    }

    boolean tpInited = false;
    boolean qsInited = false;

    //Get the properties of threadexecution, org quartz. ThreadExecutor. Class to set the properties of threadexecution. The default uses defaultthreadexecution to process threads.
    // 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).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 = new DefaultThreadExecutor();
    }


    //All preparations have been completed and start the scheduling task
    // Fire everything up
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    try {
            

        JobRunShellFactory jrsf = null; // Create correct run-shell factory...

        if (userTXLocation != null) {
            UserTransactionHelper.setUserTxLocation(userTXLocation);
        }
        //Set attribute org quartz. scheduler. Wrapjobexecutioninusertransaction affects instances of jobrunshellfactory. The default is false, and the instance is JTAAnnotationAwareJobRunShellFactory
        if (wrapJobInTx) {
            jrsf = new JTAJobRunShellFactory();
        } else {
            //The accusation of his instance is to create a JobRunShell instance, which is used by the quartz scheduler instance. This implementation will create a new JTAJobRunShell instance every time it runs the borowjobrunshell () method. When initializing, put the task into the factory.
            jrsf = new JTAAnnotationAwareJobRunShellFactory();
        }

        //Whether the id is set automatically. The default RAMJobStore does not support cluster. Therefore, when auto is set, the task id is NON_CLUSTERED. 
        if (autoId) {
            try {
              schedInstId = DEFAULT_INSTANCE_ID;
              if (js.isClustered()) {
                  schedInstId = instanceIdGenerator.generateInstanceId();
              }
            } catch (Exception e) {
                getLog().error("Couldn't generate instance Id!", e);
                throw new IllegalStateException("Cannot run without an instance id.");
            }
        }
        //The default RAMJobStore is not under this package, so don't read it
        if (js.getClass().getName().startsWith("org.terracotta.quartz")) {
            try {
                String uuid = (String) js.getClass().getMethod("getUUID").invoke(js);
                if(schedInstId.equals(DEFAULT_INSTANCE_ID)) {
                    schedInstId = "TERRACOTTA_CLUSTERED,node=" + uuid;
                    if (jmxObjectName == null) {
                        jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId);
                    }
                } else if(jmxObjectName == null) {
                    jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId + ",node=" + uuid);
                }
            } catch(Exception e) {
                throw new RuntimeException("Problem obtaining node id from TerracottaJobStore.", e);
            }

            if(null == cfg.getStringProperty(PROP_SCHED_JMX_EXPORT)) {
                jmxExport = true;
            }
        }
        //The default RAMJobStore does not implement JobStoreSupport, so it will not go here
        if (js instanceof JobStoreSupport) {
            JobStoreSupport jjs = (JobStoreSupport)js;
            jjs.setDbRetryInterval(dbFailureRetry);
            if(threadsInheritInitalizersClassLoader)
                jjs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader);
            
            jjs.setThreadExecutor(threadExecutor);
        }
        //Set QuartzSchedulerResources to plug in all the properties needed later for subsequent logical processing
        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);

        //Set the rest service configuration. This function configuration is not supported in some versions because it is commented out, that is, you can restful ly access and manage quartz scheduling tasks.
        if (managementRESTServiceEnabled) {
            ManagementRESTServiceConfiguration managementRESTServiceConfiguration = new ManagementRESTServiceConfiguration();
            managementRESTServiceConfiguration.setBind(managementRESTServiceHostAndPort);
            managementRESTServiceConfiguration.setEnabled(managementRESTServiceEnabled);
            rsrcs.setManagementRESTServiceConfiguration(managementRESTServiceConfiguration);
        }
        //Sets whether rmiExport is exposed
        if (rmiExport) {
            rsrcs.setRMIRegistryHost(rmiHost);
            rsrcs.setRMIRegistryPort(rmiPort);
            rsrcs.setRMIServerPort(rmiServerPort);
            rsrcs.setRMICreateRegistryStrategy(rmiCreateRegistry);
            rsrcs.setRMIBindName(rmiBindName);
        }
        //Set the task name and id of threadpool
        SchedulerDetailsSetter.setDetails(tp, schedName, schedInstId);

        //Set thread executor
        rsrcs.setThreadExecutor(threadExecutor);
        //Initialize threader
        threadExecutor.initialize();
        //Set thread pool
        rsrcs.setThreadPool(tp);
        if(tp instanceof SimpleThreadPool) {
            if(threadsInheritInitalizersClassLoader)
                ((SimpleThreadPool)tp).setThreadsInheritContextClassLoaderOfInitializingThread(threadsInheritInitalizersClassLoader);
        }
        //Thread pool initialization
        tp.initialize();
        tpInited = true;

        //Set jobstore
        rsrcs.setJobStore(js);

        //Set scheduling plug-in
        // add plugins
        for (int i = 0; i < plugins.length; i++) {
            rsrcs.addSchedulerPlugin(plugins[i]);
        }
        //Instantiate QuartzScheduler
        qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
        qsInited = true;

        // Create Scheduler ref...
        //Encapsulate the QuartzScheduler and qs created above into the Scheduler. The real instance is StdScheduler, new StdScheduler(qs);
        Scheduler scheduler = instantiate(rsrcs, qs);

        // set job factory if specified
        if(jobFactory != null) {
            qs.setJobFactory(jobFactory);
        }

        // Initialize plugins now that we have a Scheduler instance.
        //Initialize task plug-in
        for (int i = 0; i < plugins.length; i++) {
            plugins[i].initialize(pluginNames[i], scheduler, loadHelper);
        }
        //Add job listener
        // add listeners
        for (int i = 0; i < jobListeners.length; i++) {
            qs.getListenerManager().addJobListener(jobListeners[i], EverythingMatcher.allJobs());
        }
        //Add trigger listener 
        for (int i = 0; i < triggerListeners.length; i++) {
            qs.getListenerManager().addTriggerListener(triggerListeners[i], EverythingMatcher.allTriggers());
        }
        //Set task context data org quartz. context. key. myname=ffyy org. quartz. context. key. myaddress=ffyy11
        // set scheduler context data...
        for(Object key: schedCtxtProps.keySet()) {
            String val = schedCtxtProps.getProperty((String) key);    
            scheduler.getContext().put((String)key, val);
        }
        //Start the job store and runshell factory
        // fire up job store, and runshell factory

        js.setInstanceId(schedInstId);
        js.setInstanceName(schedName);
        js.setThreadPoolSize(tp.getPoolSize());
        js.initialize(loadHelper, qs.getSchedulerSignaler());
        //The job run shell factory just puts the scheduler in and does nothing else 
        jrsf.initialize(scheduler);
        

        //quartzscheduler initialization, including registering rmi to the local and jmx to the local mbeanserver, can be managed remotely, The attribute of registering rmi or jmx locally is export, while the attribute of quartz as the client is proxy. For example, rmiproxy is used to open rmi client remote access, and jmxproxy is used to open jmx remote management. However, jmxproxy's class needs to be implemented by itself, and rmiproxy's class has been implemented. java itself supports rmi.
        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);
        }
        //Note that the bound is the scheduler and the instance of StdScheduler
        schedRep.bind(scheduler);
        return scheduler;
    }
    catch(SchedulerException e) {
        shutdownFromInstantiateException(tp, qs, tpInited, qsInited);
        throw e;
    }
    catch(RuntimeException re) {
        shutdownFromInstantiateException(tp, qs, tpInited, qsInited);
        throw re;
    }
    catch(Error re) {
        shutdownFromInstantiateException(tp, qs, tpInited, qsInited);
        throw re;
    }
}

Analysis Scheduler interface:
The main interface of Quartz task. A scheduler maintains a registry for JobDetail and Trigger. Once registered, the scheduler's task is to execute the specified job when the Trigger is triggered. Scheduler instances are created through SchedulerFactory. A scheduler that has been created or initialized can be discovered and used by the same factory that created it. According to the test, even if the StdSchedulerFactory is re instantiated, the scheduler is indeed the same because a single SchedulerRepository is maintained. Our custom task needs to implement org Quartz. Job interface, and then scheduleJob(JobDetail, Trigger), or addJob(JobDetail, boolean). SimpleTrigger is most frequently used. It is repeated many times at a certain time. Given a delay, CronTrigger is also allowed to be supported based on days, the day of the week, the number of days of each month, or the month of each year. Both job and Trigger have names and groups associated with them, which should be unique in scheduler. The group feature is used for logical classification or category classification. If you do not need to use a group, you can use the default group, scheduler DEFAULT_ GROUP´╝î
Jobs can also be triggered manually. triggerJob(String jobName, String jobGroup) can be used. The client program may also be interested in whether monitoring can be implemented in quartz. Fortunately, JobListener can realize this and notify users. TriggerListener provides monitoring on the implementation of triggers, SchedulerListener also realizes the listening of Scheduler events or errors. Listeners can be associated with local schedulers through the listener manager.
SchedulerFactory factory1 = new StdSchedulerFactory();
Scheduler scheduler1 = factory1.getScheduler();
SchedulerFactory factory2 = new StdSchedulerFactory();
Scheduler scheduler2 = factory2.getScheduler();
System.out.println(scheduler1.equals(scheduler2));
System.out.println(scheduler1 == scheduler2);

The above is the analysis of all the source code. The following analysis is run when actively scheduling a task.
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();

// Trigger the job to run on the next round minute
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();

// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger); // It's not really started. You have to start the task

sched.start();

sched. Source code of schedulejob (job, trigger):

validateState();

    if (jobDetail == null) {
        throw new SchedulerException("JobDetail cannot be null");
    }
    
    if (trigger == null) {
        throw new SchedulerException("Trigger cannot be null");
    }
    
    if (jobDetail.getKey() == null) {
        throw new SchedulerException("Job's key cannot be null");
    }

    if (jobDetail.getJobClass() == null) {
        throw new SchedulerException("Job's class cannot be null");
    }
    
    OperableTrigger trig = (OperableTrigger)trigger;

    if (trigger.getJobKey() == null) {
        trig.setJobKey(jobDetail.getKey());
    } else if (!trigger.getJobKey().equals(jobDetail.getKey())) {
        throw new SchedulerException(
            "Trigger does not reference given job!");
    }

    trig.validate();

    Calendar cal = null;
    if (trigger.getCalendarName() != null) {
        cal = resources.getJobStore().retrieveCalendar(trigger.getCalendarName());
    }
    Date ft = trig.computeFirstFireTime(cal);

    if (ft == null) {
        throw new SchedulerException(
                "Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire.");
    }

    resources.getJobStore().storeJobAndTrigger(jobDetail, trig);
    notifySchedulerListenersJobAdded(jobDetail);
    //Notifies the thread of the next start time
    notifySchedulerThread(trigger.getNextFireTime().getTime());
    notifySchedulerListenersSchduled(trigger);

    return ft;

sched.start(); Source code analysis:

In fact, the start method of StdScheduler is called. StdScheduler encapsulates QuartzScheduler, which is actually the start method of QuartzScheduler,
public void start() throws SchedulerException {
//Is it closed
if (shuttingDown|| closed) {
throw new SchedulerException(
"The Scheduler cannot be restarted after shutdown() has been called.");
}

    // QTZ-212 : calling new schedulerStarting() method on the listeners
    // right after entering start()
    notifySchedulerListenersStarting(); // Based on the event mechanism, the listener is notified to call the schedulerStarting() method of SchedulerListener

    if (initialStart == null) {
        initialStart = new Date();
        this.resources.getJobStore().schedulerStarted();   //Call the schedulerStarted method of RAMJobStore. RAMJobStore does nothing, and the method is empty
        startPlugins();//start() method of batch iteration start SchedulerPlugin
    } else {
        resources.getJobStore().schedulerResumed();
    }

    schedThread.togglePause(false);  //Whether to open or close the main thread, that is, QuartzSchedulerThread. QuartzSchedulerThread encapsulates all other task threads. The most important point of QuartzScheduler is.... Five star important...

    getLog().info(
            "Scheduler " + resources.getUniqueIdentifier() + " started.");
    
    notifySchedulerListenersStarted();// Based on the event mechanism, the listener is notified to call the schedulerStarted() method of SchedulerListener
}

QuartzSchedulerThread resolves connections:
Detailed explanation of quartz quartzschedulerthread_ ImPler's blog CSDN blog_ quartzschedulerthread

Topics: Quartz