QuartzScheduler源码分析

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

针对于以上源码进行解析:

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

StdSchedulerFactory是SchedulerFactory的一个实现类,主要目的是用Properties来创建QuartzScheduler的实例。
默认的,quartz.properties是从当前工作目录去载入的,如果失败了,则在包目录下org/quartz下的quartz.properties去搜寻,如果你想要自定义一个文件,你需要设置org.quartz.properties系统属性指向那个文件。
你在调用getScheduler()方法之前,需要先调用initialize(xx)方法来初始化这个工厂。
你可以看到样品properties文件
JobStore,ThreadPool实例或者其他spi类将被创建,其他任何指定的属性将等同于调用set方法,举个例子,在properties配置文件中设置了属性org.quartz.jobStore.myProp = 10,在jobstore实例话后,方法setMyProp()将被调用。在方法setMyProp()将被调用之前,类型会转化成原始类型。一个property可以指向另一个property的值,比如用例:$@org.quartz.scheduler.instanceName指向同一个值

sf.getScheduler();
该代码全部代码如下:
public Scheduler getScheduler() throws SchedulerException {
if (cfg == null) {
initialize(); //初始化
}

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

SchedulerRepository是Scheduler的存储仓库,采用了单利模式,维护了一个map,schedulers = new HashMap<String, Scheduler>();
为何引入SchedulerRepository?
目的是不需要再单独维护map,且是单利的Scheduler。最重要的是封装一个map,仅仅暴露出了仅有的几个方法操作比如lookup,避免暴露出map,减少用户使用错误的发生。

分析initialize()初始化方法:
用Properties文件和覆盖的系统properties来初始化SchedulerFactory。
系统properties会覆盖文件里的properties。
默认的,quartz.properties是从当前工作目录去载入的,如果失败了,则在包目录下org/quartz下的quartz.properties去搜寻,如果你想要自定义一个文件,你需要设置org.quartz.properties系统属性指向那个文件。

初始化,将文件里面的properties属性写入到props里面,然后将props封装到PropertiesParser类里。
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 {
            //没有指定system属性,使用默认的文件
            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()));
}

分析instantiate()实例化方法:
这代码太冗长了 , 看起来有点不专业哈。咱一行一行来吧。

private Scheduler instantiate() throws SchedulerException {
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      = SchedulerRepository.getInstance();

    // Get Scheduler Properties
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //任务调度名称
    String schedName = cfg.getStringProperty(PROP_SCHED_INSTANCE_NAME,
            "QuartzScheduler");
    //线程名称
    String threadName = cfg.getStringProperty(PROP_SCHED_THREAD_NAME,
            schedName + "_QuartzSchedulerThread");
    //任务调度实例id
    String schedInstId = cfg.getStringProperty(PROP_SCHED_INSTANCE_ID,
            DEFAULT_INSTANCE_ID);
    //schedInstId 可以为AUTO 会得到计算机名, SYS_PROP 需要设置属性org.quartz.scheduler.instanceId,并不是从文件里面读取的,而是读取的系统变量
    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不能同时激活
    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);

    //第一阶段返回 
    // If Proxying to remote scheduler, short-circuit here...
    // ~~~~~~~~~~~~~~~~~~
    //当选择了rmi暴露之后,就只能请求真实的调度机器的地址去请求调度,而不是本机也成为一个调度任务服务器,类似于本机quartz只是一个quartz客户端,去请求真实的调度任务机器,直接返回。
    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();


    //第二阶段返回 类似于上面的rmi远程过程调用,只不过协议换成了jmx的远程过程调用
    //决定了是不是连接远程的jmx,意思就是说本身只作为quartz的客户端。但是需要自己提供实现来实现通信。
    // 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;
    }

    //job工厂 默认为null ,需要自己提供实现
    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;
        }
    }
    //任务id生成器  看org.quartz.scheduler.instanceId属性值确定class值,  若是AUTO 则是SimpleInstanceIdGenerator,若是SYS_PROP 则是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;
        }
    }
    //获取线程池属性 , 默认线程池是SimpleThreadPool , 可以通过设置org.quartz.threadPool.name=1设置线程池名称为1,等其他属性也是这么设置的。具体属性设置查看类实例。
    // 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;
    }
    //获取jobstore的属性,默认使用的是RAMJobStore实例
    // 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;
    }
    //设置jobstore的名称和id
    SchedulerDetailsSetter.setDetails(js, schedName, schedInstId);

    //设置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;
    }
    //设置自定义的jobstore的lockhandler
    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;
            }
        }
    } 
    //设置多个数据库信息 可以这样设置 org.quartz.dataSource.db1= org.quartz.dataSource.db2= 这是两个数据库
    // 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);

        //设置对应的数据源的connectionProvider , 属性为org.quartz.dataSource.db1.connectionProvider.class=
        // custom connectionProvider...
        if(cpClass != null) {
            ConnectionProvider cp = null;
            try {
                //实例化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);
                //如果是PoolingConnectionProvider的实例,
                if (cp instanceof PoolingConnectionProvider) {
                    populateProviderWithExtraProps((PoolingConnectionProvider)cp, pp.getUnderlyingProperties());
                } else {
                    //设置connectionprovider属性
                    setBeanProps(cp, pp.getUnderlyingProperties());
                }
                //初始化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 {
            //如果没有connectionprovider设置的话 设置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 {
                //设置所有的数据源公用的connectionprovider
                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
                //根据provider类别设置共用的connectionprovider ,如果都没有设置的话默认的是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;
                    }
                    //将所有的connectionprovider放入到dbconnectionmanager里面管理
                    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.plugin.plugin1= 这样设置插件 可以设置多个 ,org.quartz.plugin.plugin2.class=  , 需要实现SchedulerPlugin接口 , 只是设置还没有应用到
    // 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;
    }
    //设置JobListeners , org.quartz.jobListener.listen1= , 实现JobListener , 调用setName设置名称。 设置其他的属性,也只是设置joblisten,并没有应用
    // 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;
    }
    //安装org.quartz.triggerListener, 设置多个listener,org.quartz.triggerListener.listen1= ,调用setName方法设置名称,从配置文件里设置其他的属性,
    // 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;

    //获取ThreadExecutor的属性,org.quartz.threadExecutor.class , 设置ThreadExecutor的属性。默认的使用DefaultThreadExecutor来处理线程。
    // 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();
    }


    //所有的准备工作已经完成,开始启动调度任务
    // Fire everything up
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    try {
            

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

        if (userTXLocation != null) {
            UserTransactionHelper.setUserTxLocation(userTXLocation);
        }
        //设置属性org.quartz.scheduler.wrapJobExecutionInUserTransaction 影响到jobrunshellfactory的实例。默认的是false ,实例是JTAAnnotationAwareJobRunShellFactory
        if (wrapJobInTx) {
            jrsf = new JTAJobRunShellFactory();
        } else {
            //他的实例的指责是创建JobRunShell实例, 被 quartz scheduler实例使用,这个实现每次运行borrowJobRunShell()这个方法的时候,都会创建新的JTAJobRunShell实例。初始化的时候把任务放到factory里。
            jrsf = new JTAAnnotationAwareJobRunShellFactory();
        }

        //是否是自动的设置id的 , 默认的RAMJobStore不支持集群的。所以设置auto的情况下,任务id是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.");
            }
        }
        //默认的RAMJobStore不是在这个包下的 ,所以不用看了
        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;
            }
        }
        //默认的RAMJobStore没实现JobStoreSupport,所以不会走这里
        if (js instanceof JobStoreSupport) {
            JobStoreSupport jjs = (JobStoreSupport)js;
            jjs.setDbRetryInterval(dbFailureRetry);
            if(threadsInheritInitalizersClassLoader)
                jjs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader);
            
            jjs.setThreadExecutor(threadExecutor);
        }
        //设置QuartzSchedulerResources  把所有后面需要用到的很多属性塞到这里 ,用于后续的一些逻辑处理
        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);

        //设置rest服务配置  , 有些版本里面不支持这个功能配置 ,是因为被注释掉了,就是可以restful访问管理quartz调度任务。
        if (managementRESTServiceEnabled) {
            ManagementRESTServiceConfiguration managementRESTServiceConfiguration = new ManagementRESTServiceConfiguration();
            managementRESTServiceConfiguration.setBind(managementRESTServiceHostAndPort);
            managementRESTServiceConfiguration.setEnabled(managementRESTServiceEnabled);
            rsrcs.setManagementRESTServiceConfiguration(managementRESTServiceConfiguration);
        }
        //设置是否暴露rmiExport
        if (rmiExport) {
            rsrcs.setRMIRegistryHost(rmiHost);
            rsrcs.setRMIRegistryPort(rmiPort);
            rsrcs.setRMIServerPort(rmiServerPort);
            rsrcs.setRMICreateRegistryStrategy(rmiCreateRegistry);
            rsrcs.setRMIBindName(rmiBindName);
        }
        //设置threadpool的任务名称,  id
        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;

        //设置jobstore
        rsrcs.setJobStore(js);

        //设置调度插件
        // add plugins
        for (int i = 0; i < plugins.length; i++) {
            rsrcs.addSchedulerPlugin(plugins[i]);
        }
        //实例化QuartzScheduler
        qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
        qsInited = true;

        // Create Scheduler ref...
        //将上面创建的QuartzScheduler , qs封装到Scheduler里 ,真实的实例是 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.
        //初始化任务插件
        for (int i = 0; i < plugins.length; i++) {
            plugins[i].initialize(pluginNames[i], scheduler, loadHelper);
        }
        //添加job监听器
        // add listeners
        for (int i = 0; i < jobListeners.length; i++) {
            qs.getListenerManager().addJobListener(jobListeners[i], EverythingMatcher.allJobs());
        }
        //添加触发器监听器 
        for (int i = 0; i < triggerListeners.length; i++) {
            qs.getListenerManager().addTriggerListener(triggerListeners[i], EverythingMatcher.allTriggers());
        }
        //设置任务上下文数据  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);
        }
        //点火启动 job store, runshell factory
        // fire up job store, and runshell factory

        js.setInstanceId(schedInstId);
        js.setInstanceName(schedName);
        js.setThreadPoolSize(tp.getPoolSize());
        js.initialize(loadHelper, qs.getSchedulerSignaler());
        //job run shell factory 只是把scheduler放进去 ,没做什么别的操作 
        jrsf.initialize(scheduler);
        

        //quartzscheduler初始化 , 包含注册rmi到本地 , 注册jmx到本地mbeanserver可以被远程管理。 ,注册rmi或者jmx到本地的属性是 export , 而把quartz作为客户端的属性是proxy,比如开启rmi客户端远程访问是rmiproxy,开启jmx远程管理的属性是jmxproxy,但是jmxproxy的class需要自己实现,而rmiproxy的class是已经实现好了,java本身就支持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);
        }
        //注意 , 绑定的是scheduler ,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;
    }
}

分析Scheduler接口:
Quartz任务的主要接口,一个Scheduler维持着一个注册表,关于JobDetail和Trigger,一旦注册,Scheduler的任务就是在触发器触发的时候,执行指定的job。Scheduler实例是通过SchedulerFactory创建的,一个已经创建或者初始化的Scheduler能够被同一个创建他的工厂所发现和使用。根据测试发现就算重新实例StdSchedulerFactory但是Scheduler确实同一个,因为维护了一个单例的SchedulerRepository。我们自定义的任务需要实现org.quartz.Job接口,然后scheduleJob(JobDetail, Trigger),或者addJob(JobDetail, boolean)。SimpleTrigger是最经常使用的,在一个确定的时间,重复多次,给定的一个延迟,CronTrigger也允许基于天,每个星期的星期几,每个月的多少号,或者每年的几月份都是支持的。job和Trigger都有名字和组关联到他们,在Scheduler中应当是独一无二的。group这个特征用于逻辑分类,或者类目分类,如果你不需要使用到组,你可以使用默认的组,Scheduler.DEFAULT_GROUP,
job也能够被手动的触发,可以使用triggerJob(String jobName, String jobGroup),客户端的程序可能对是否能在quartz里实现监听也感兴趣,幸好,JobListener能实现这个,对用户进行通知,关于job的执行情况,TriggerListener提供了对trigger的执行情况的监听,SchedulerListener也实现了对于Scheduler事件或者错误的监听,监听器能够用local schedulers通过ListenerManager关联在一起。
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);

以上就是所有的源码的分析, 以下分析运行时,当主动调度一个任务的时候 。
// 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); //并不是真的启动 要启动任务才行

sched.start();

sched.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);
    //通知线程下一次的启动时间
    notifySchedulerThread(trigger.getNextFireTime().getTime());
    notifySchedulerListenersSchduled(trigger);

    return ft;

sched.start();的源码解析:

其实调用的是StdScheduler的start方法,StdScheduler封装了QuartzScheduler,其实就是QuartzScheduler的start方法,
public void start() throws SchedulerException {
//是否已经关闭
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(); // 基于事件机制通知监听器调用SchedulerListener的schedulerStarting()方法

    if (initialStart == null) {
        initialStart = new Date();
        this.resources.getJobStore().schedulerStarted();   //调用RAMJobStore的 schedulerStarted方法 ,         RAMJobStore什么也没做 ,空方法
        startPlugins();//批量迭代启动SchedulerPlugin的start()方法
    } else {
        resources.getJobStore().schedulerResumed();
    }

    schedThread.togglePause(false);  //是否开启主线程的开启或者关闭,也就是QuartzSchedulerThread,QuartzSchedulerThread封装了其他的所有的任务线程,QuartzScheduler最重要的点在这。。。。五星级重要。。。

    getLog().info(
            "Scheduler " + resources.getUniqueIdentifier() + " started.");
    
    notifySchedulerListenersStarted();// 基于事件机制通知监听器调用SchedulerListener的schedulerStarted()方法
}

QuartzSchedulerThread解析连接:
Quartz-QuartzSchedulerThread详解_Impler's blog-CSDN博客_quartzschedulerthread

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一.西式快餐的管理 在竞争激烈的快餐市场中,西式快餐总能立于潮头,是因为它有着第一流的管理。西 式快餐提出的目标是100%的顾客满意。即顾客在西式快餐所能得到的服务要多于他原来 所期望得到的服务。这就要求员工要有敬业精神,对顾客的服务要细致入微。优质的产 品(Q),快速友善的服务(S),清洁卫生(C)的用餐环境以及物超所值(V)是西式 快餐管理的四大要素。 Q.S.C.V. 产品质量,即QSCV中的"Q"(Quality) 所选产品其重量,质量,卫生状况,加工要求,包装,运输,储存等都有非常严格的 标准。例如,严格按照规程调味裹粉,然后放入自动高速的压力炸锅中烹炸,时间及温 度均由电脑控制。这样,鸡肉内层鲜嫩多汁,外层香脆可口,风味独特的原味鸡便呈现在 顾客面前了。同时,为保证每块鸡的质量及口味绝对让顾客满意,炸好以后必须在保存 时间内售出,否则炸鸡必须废弃。统一的标准,规程,时间和方法,使顾客无论在今天 ,还是在明天,都能品尝到品质相同的炸鸡。 优质服务,即QSCV中的"S"(Service) 西式快餐的服务要求让顾客感受到亲切,舒适,迅速,要尽量满足顾客的要求。收银 员必须严格按照收银规范来操作,不得有半点马虎。西式快餐还有一套接待员制度,接 待员会帮助顾客点餐,为顾客领位,在儿童游乐区照看正在玩耍的孩子,还在一定的时 候组织大型的儿童生日餐会,细致入微地服务,使顾客有宾至如归的用餐感受。 清洁卫生,即QSCV中的"C"(Clean) 西式快餐有一套严格的完整的清洁卫生制度,它包括:随手清洁,以及每日,每周及 每月的例行清洁。餐厅的每一位员工都会运用不同的清洁工具进行不同的清洁工作,随 手清洁是一种传统。每一位员工都会小心,爱护,留意(TLC)给每一位顾客留下美好的 用餐经验。 物超所值,即QSCV中的"V"(Value) 物超所值不仅表现在美味的产品上,还在于消费者在合理的价格之内,享受到的是值 得信赖的品质,亲切礼貌的服务和舒适卫生的用餐环境。 二.展望 成为快餐行业中最有影响及最受欢迎的餐厅品牌。 我们要试图去了解顾客的真正需要,并采取一切措施去满足其需要,而最重要的原则 是"以客为因"。 三.顾客再次光临的等式 产品质量 + 产品价值 + 服务质量+用餐环境 = 再次光临的决定 占31% + 占13% + ( 占 56% ) = 100% 四.顾客的期望 1).餐厅清洁; 2).员工友善; 3).提供食品准确; 4).设施管理妥善; 5).食品优异,质量稳定; 6).服务迅速。 五.顾客抱怨 * 请记住:即使我们不认为自己做错了,但顾客永远是最重要的。 A.有些抱怨是餐厅内任何一位员工都能处理的,例如: 1).餐点不正确; 2).包装不正确; 3).产品质量有问题; 4).服务态度; 5).桌椅不干净等。 B.必需由餐厅经理,值班经理解决的问题: 1).食物中毒,或食品安全引起的疾病; 2).食品污染; 3).食品中有异物; 4).突发事件,伤害或受伤; 5).员工处理后,未能使顾客满意的抱怨; 6).顾客要求公司,管理部门出面解决的抱怨; C.处理顾客抱怨的基本程序: 专注倾听: 1).仔细倾听,让顾客感受到我们是真诚的了解及处理问题; 2).目光注视顾客,表示尊重; 3).确认完全了解顾客的问题; 4).了解事实; 5).肢体语言表达我们对问题的关心; 6).千万不要动怒,并有意解决问题; 7).判断属于何种性质。 表示关心: 1).无论谁对谁错,一定要表示我们对问题的关心; 2).表示真诚的态度; 3).表达如"我很遗憾发生这种事情"之类的话语; 4).建议合理的解决方式,征求顾客的意见; 5).在可能的情况下,为顾客更换产品,或更正错误餐点或退款。 使顾客满意: 1).使顾客满意—立即解决问题; 2).如果是员工不能解决的问题,应请值班经理处理; 3).在处理问题的过程中,经理的亲自参与是很重要的。 感谢顾客: 1).感谢顾客提出抱怨,使我们有机会解决问题; 2).再次表达我们对问题的关心; 3).将顾客的抱怨及我们采取的解决方法,通知值班经理。 D.处理抱怨的主要原则: 1).耐心倾听顾客的抱怨后先道歉,然后请顾客稍候,并告诉他们你立即去找值班经理来 处理; 2).如顾客很生气,你绝对不要与顾客顶撞,应有礼貌的先道歉并请值班经理来服务顾客 ; 3).立即报告值班经理,并简述经过,带值班经理到顾客面前,然后,回到自己的工作岗 位。 E.处理抱怨的基本原则: 1).友善及乐意协助的态度; 2).要冷静,不要企图解释或辩护; 3).要用"请,很抱歉,请稍后"的语气; 4).立即请求管理组协助,由管理解决; 5).决不能让顾客不高兴的离开。 F.工作优先的次序: 1).直接影响到顾客方便的事先做。 2).再处理间

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值