美文网首页
quart初始化流程

quart初始化流程

作者: xiaoming_he | 来源:发表于2019-08-09 18:13 被阅读0次

    研究源码,从简单使用开始,跑一遍demo后,再研究是如何初始化的,我们先研究以下的代码:

    public class QuartzSimpleDemo {
    
        public static void main(String[] args) throws SchedulerException {
            //1\. 创建Scheduler
            SchedulerFactory sfact = new StdSchedulerFactory();
            Scheduler sched  = sfact.getScheduler();
    
            //2\. 创建job信息
            JobDetail testTaskJob = JobBuilder.newJob(TestTaskJob.class).storeDurably()
                    .withIdentity("TestTaskJob").build();
            //3\. 创建触发器
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?");
            CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                    .forJob(testTaskJob)
                    .withIdentity("testTaskJob")
                    .withSchedule(cronScheduleBuilder)
                    .build();
            //4\. 启动
            sched.scheduleJob(testTaskJob, cronTrigger);
            sched.start();
        }
    }
    
    

    quartz原生初始化

    [图片上传中...(image-97984b-1565345621214-0)]

    1. 创建Scheduler

    SchedulerFactory sfact = new StdSchedulerFactory();
    
    

    工厂模式创建一个Scheduler工厂,quartz中有两个实现类,DirectSchedulerFactory和StdSchedulerFactory,常用StdSchedulerFactory类。

    Scheduler sched  = sfact.getScheduler();
    
    

    从工厂中获取一个Scheduler对象,这个是我们研究的重点。我们看下getSchedulerd的逻辑

    public Scheduler getScheduler() throws SchedulerException {
            //初始化配置文件
                if (cfg == null) {
                initialize();
            }
    
            SchedulerRepository schedRep = SchedulerRepository.getInstance();
                //根据scheduler名字从缓存中获取Scheduler对象
            Scheduler sched = schedRep.lookup(getSchedulerName());
    
            if (sched != null) {
                if (sched.isShutdown()) {
                    schedRep.remove(getSchedulerName());
                } else {
                    return sched;
                }
            }
    
                //如果缓存中没有,则初始化
            sched = instantiate();
    
            return sched;
        }
    
    

    1.1 初始化配置

    • 如果创建SchedulerFactory时,传了配置文件,则使用传入的

    • 如果创建SchedulerFactory时没有传,则看系统参数中配置了org.quartz.properties,也就-Dorg.quartz.properties是否配置。

    • 如果系统参数没有传,或者传了找不到,则在各种路径中查找quartz.properties文件。所以建议直接在配置文件中新增quartz.properties文件就可以。

    1.2 缓存中加载Scheduler

    上面代码是从SchedulerRepository获取缓存的Scheduler,SchedulerRepository中有一个全局的HashMap,key是Scheduler名,值是Scheduler对象,开始时肯定是空,所以调用instantiate方法.

    1.3 初始化

    如果缓存中没有,则创建 Scheduler对象,看下创建逻辑,创建逻辑主要是读取配置,根据配置创建对应的类.这个方法有点长,不过注释写的好,很容易看懂,我留下主要的代码

    private Scheduler instantiate() throws SchedulerException {
            // Get Scheduler Properties
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            // If Proxying to remote scheduler, short-circuit here...
            // ~~~~~~~~~~~~~~~~~
    
            // Create class load helper
    
            // If Proxying to remote JMX scheduler, short-circuit here...
            // ~~~~~~~~~~~~~~~~~~
    
            // Get ThreadPool Properties
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            // Get JobStore Properties
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            // Set up any DataSources
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            // Set up any SchedulerPlugins
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            // Set up any JobListeners
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            // Set up any TriggerListeners
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            // Get ThreadExecutor Properties
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            // Fire everything up
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
                // Create correct run-shell factory...
    
           //create QuartzSchedulerResources
           // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           QuartzSchedulerResources rsrcs = new QuartzSchedulerResources();
           //create QuartzScheduler
           // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
            qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
            qsInited = true;
    
            // Create Scheduler ref...
            Scheduler scheduler = instantiate(rsrcs, qs);
    
            schedRep.bind(scheduler);
            return scheduler;
            }
        }
    
    

    上面的代码,我把代码都删了,留下注释和几行核心的代码,看起来更清晰。主要逻辑是根据配置创建不同的类。然后把所有的类都封装到QuartzSchedulerResources对象中。然后再创建QuartzScheduler对象。Scheduler大多数操作都是依赖QuartzScheduler完成的。最后创建Scheduler对象,并把Scheduler对象存到缓存中。

    QuartzScheduler创建时启动了生产者线程,到研究quartz线程模型的时候会写到.

    public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)
            throws SchedulerException {
            this.resources = resources;
            if (resources.getJobStore() instanceof JobListener) {
                addInternalJobListener((JobListener)resources.getJobStore());
            }
    
            this.schedThread = new QuartzSchedulerThread(this, resources);
            ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
            schedThreadExecutor.execute(this.schedThread);
            if (idleWaitTime > 0) {
                this.schedThread.setIdleWaitTime(idleWaitTime);
            }
        }
    
    

    2. 创建JobDetail

    利用构造者模式创建JobDetail对象,比较简单

    3. 创建Trigger

    利用构造者模式创建Trigger对象,比较简单。

    4. 调度Job

    public Date scheduleJob(JobDetail jobDetail,
                Trigger trigger) throws SchedulerException {
            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;
        }
    
    

    job调度,开始参数校验,然后存储job和trigger,然后通知各种监听器。

    5. 启动

    启动主要也是通知监听事情,表示Scheduler启动起来了。

    spring boot quartz初始化

    1. 查找配置类

    spring boot quartz的使用我们并没有使用任何注解就能够使用,那它是如何初始化的呢?因为使用了spring boot,所以我猜测使用了spring boot的自动配置功能,所以我用IDEA搜索了QuartzAutoConfiguration,还真让我搜到了。如果不知道spring boot的加载过程,可以查看SchedulerFactory#getScheduler被哪里调用了,反推初始化过程。

    @Configuration
    @ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class })
    @EnableConfigurationProperties(QuartzProperties.class)
    @AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
    public class QuartzAutoConfiguration {}
    
    

    QuartzAutoConfiguration使用了@Configuration注解,在spring boot启动的时候就会加载,当系统中有Scheduler相关的类时,就会创建对应的bean。

    2. 构造函数

    public QuartzAutoConfiguration(QuartzProperties properties,
                ObjectProvider<SchedulerFactoryBeanCustomizer> customizers, ObjectProvider<JobDetail[]> jobDetails,
                Map<String, Calendar> calendars, ObjectProvider<Trigger[]> triggers,
                ApplicationContext applicationContext) {
            this.properties = properties;
            this.customizers = customizers;
            this.jobDetails = jobDetails.getIfAvailable();
            this.calendars = calendars;
            this.triggers = triggers.getIfAvailable();
            this.applicationContext = applicationContext;
        }
    
    

    构造函数中注入了jobDetail和trigger。

    3. SchedulerFactoryBean

    @Bean
        @ConditionalOnMissingBean
        public SchedulerFactoryBean quartzScheduler() {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
            jobFactory.setApplicationContext(this.applicationContext);
            schedulerFactoryBean.setJobFactory(jobFactory);
            if (this.properties.getSchedulerName() != null) {
                schedulerFactoryBean.setSchedulerName(this.properties.getSchedulerName());
            }
            schedulerFactoryBean.setAutoStartup(this.properties.isAutoStartup());
            schedulerFactoryBean.setStartupDelay((int) this.properties.getStartupDelay().getSeconds());
            schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(this.properties.isWaitForJobsToCompleteOnShutdown());
            schedulerFactoryBean.setOverwriteExistingJobs(this.properties.isOverwriteExistingJobs());
            if (!this.properties.getProperties().isEmpty()) {
                schedulerFactoryBean.setQuartzProperties(asProperties(this.properties.getProperties()));
            }
            if (this.jobDetails != null && this.jobDetails.length > 0) {
                schedulerFactoryBean.setJobDetails(this.jobDetails);
            }
            if (this.calendars != null && !this.calendars.isEmpty()) {
                schedulerFactoryBean.setCalendars(this.calendars);
            }
            if (this.triggers != null && this.triggers.length > 0) {
                schedulerFactoryBean.setTriggers(this.triggers);
            }
            customize(schedulerFactoryBean);
            return schedulerFactoryBean;
        }
    
    

    声明了一个SchedulerFactoryBean bean,设置了配置、jobDetail和trigger属性值。接下来看下SchedulerFactoryBean的初始化过程。

    public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>,
            BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
        ...
    }
    
    

    SchedulerFactoryBean实现了InitializingBean和FactoryBean,所以在初始化的时候会调用afterPropertiesSet方法,在得到bena的时候会调用getObject方法,先看afterPropertiesSet方法。

    @Override
        public void afterPropertiesSet() throws Exception {
            if (this.dataSource == null && this.nonTransactionalDataSource != null) {
                this.dataSource = this.nonTransactionalDataSource;
            }
    
            if (this.applicationContext != null && this.resourceLoader == null) {
                this.resourceLoader = this.applicationContext;
            }
    
            // Initialize the Scheduler instance...
            this.scheduler = prepareScheduler(prepareSchedulerFactory());
            try {
                registerListeners();
                registerJobsAndTriggers();
            }
            catch (Exception ex) {
                try {
                    this.scheduler.shutdown(true);
                }
                catch (Exception ex2) {
                    logger.debug("Scheduler shutdown exception after registration failure", ex2);
                }
                throw ex;
            }
        }
    
    

    看到初始化scheduler的注释,所以应该就是在prepareScheduler中初始化scheduler对象的。在创建scheduler之前,先创建了SchedulerFactory,看下创建过程。

    private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
            SchedulerFactory schedulerFactory = this.schedulerFactory;
            if (schedulerFactory == null) {
                // Create local SchedulerFactory instance (typically a StdSchedulerFactory)
                schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
                if (schedulerFactory instanceof StdSchedulerFactory) {
                    initSchedulerFactory((StdSchedulerFactory) schedulerFactory);
                }
                else if (this.configLocation != null || this.quartzProperties != null ||
                        this.taskExecutor != null || this.dataSource != null) {
                    throw new IllegalArgumentException(
                            "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
                }
                // Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties)
            }
            // Otherwise, assume that externally provided factory has been initialized with appropriate settings
            return schedulerFactory;
        }
    
    

    在SchedulerFactoryBean创建的时候并没有给schedulerFactory属性赋值,所以schedulerFactory是空的,如果是空的则加载schedulerFactoryClass类,schedulerFactoryClass的值是StdSchedulerFactory。这和我们直接使用quartz是一样的。接下来就是创建Scheduler对象。

    private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException {
            ...
            // Get Scheduler instance from SchedulerFactory.
            try {
                Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName);
                ...
                return scheduler;
            }
    
    

    prepareScheduler方法又调用了createScheduler,继续跟踪

    protected Scheduler createScheduler(SchedulerFactory schedulerFactory, @Nullable String schedulerName)
                throws SchedulerException {
    
            // Override thread context ClassLoader to work around naive Quartz ClassLoadHelper loading.
            Thread currentThread = Thread.currentThread();
            ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
            boolean overrideClassLoader = (this.resourceLoader != null &&
                    this.resourceLoader.getClassLoader() != threadContextClassLoader);
            if (overrideClassLoader) {
                currentThread.setContextClassLoader(this.resourceLoader.getClassLoader());
            }
            try {
          //从缓存中获取Scheduler
                SchedulerRepository repository = SchedulerRepository.getInstance();
                synchronized (repository) {
                    Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null);
                    Scheduler newScheduler = schedulerFactory.getScheduler();
                    if (newScheduler == existingScheduler) {
                        throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " +
                                "in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!");
                    }
                    if (!this.exposeSchedulerInRepository) {
                        // Need to remove it in this case, since Quartz shares the Scheduler instance by default!
                        SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName());
                    }
                    return newScheduler;
                }
            }
            finally {
                if (overrideClassLoader) {
                    // Reset original thread context ClassLoader.
                    currentThread.setContextClassLoader(threadContextClassLoader);
                }
            }
        }
    
    

    创建的过程和原生的quartz的逻辑是一样的了,都是先从缓存中获取,如果缓存中没有,再调用getScheduler方法获取。

    所以Scheduler的创建spring boot只是包装了一层壳,最后的逻辑和quartz原生是一样的。那么job和trigger是什么时候注入进去的呢?afterPropertiesSet方法还只分析了一个方法,还有两个方法

    registerListeners();
    registerJobsAndTriggers();
    
    

    见名知意,这两个方法就是注册监听器、job和detail的。注入的逻辑和原生没什么区别,这里就不再阐述了。

    总结

    这篇笔记分析了quartz原生的初始化过程和spring boot quartz初始化过程。spring boot初始化bean基本都是包装了一层AutoConfiguration需要的bean,底层实现还是和原生的一样。还介绍了阅读源码的技术,去除干扰,带着目标去找代码的实现。

    相关文章

      网友评论

          本文标题:quart初始化流程

          本文链接:https://www.haomeiwen.com/subject/qlfkjctx.html