Spring-Quartz企业级理解-定时任务的运行

299 阅读1分钟

在调度器的运行篇章中,可以看到在QuartzSchedulerThread的线程中,会从数据库竞争trigger,竞争到trigger后,执行trigger所绑定的job Job的运行是在SimpleThreadPool的线程中运行,回顾一下QuartzSchedulerThread.tun()

public void run() {
    ....
    int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
    // 当SimpleThreadPool中有可用线程的时候,才会进入往下走
    if(availThreadCount > 0) {
	....
    for (int i = 0; i < bndles.size(); i++) {
        TriggerFiredResult result =  bndles.get(i);
        TriggerFiredBundle bndle =  result.getTriggerFiredBundle();
        ....
        JobRunShell shell = null;
        try {
            // 把trigger和job的信息封装在JobRunShell里面
            shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
            shell.initialize(qs);
        } catch (SchedulerException se) {
  			....
        }
        // 由于JobRunShell实现了Runnable接口,所以可以将其放入线程池中跑
        if (qsRsrcs.getThreadPool().runInThread(shell) == false) {
            getLog().error("ThreadPool.runInThread() return false!");
            qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
        }
    }
	....
}

QuartzSchedulerThread.tun()方法中,先从数据库取出待运行的trigger和job的信息,储存在TriggerFiredBundle类中 由下面第二个类图可见,jobDetail类型的job字段对应job的信息,OperableTrigger类型的trigger字段对应trigger的信息 代码中从TriggerFiredResult类取出TriggerFiredBundle类型的triggerFiredBundle字段对象来初始化JobRunShell JobRunShellFactory的实现类是JTAAnnotationAwareJobRunShellFactory,JTAAnnotationAwareJobRunShellFactory.createJobRunShell()方法代码如下

public JobRunShell createJobRunShell(TriggerFiredBundle bundle) throws SchedulerException {
    // 获取job实现类的ExecuteInJTATransaction注解
    // JTA事务是一种不同于JDBC事务的事务类型
    ExecuteInJTATransaction jtaAnnotation = ClassUtils.getAnnotation(bundle.getJobDetail().getJobClass(), ExecuteInJTATransaction.class);
    if(jtaAnnotation == null)
        // 一般使用JDBC事务,也就是进入这个分支,初始化一个JobRunShell并返回
        return new JobRunShell(scheduler, bundle);
    else {
        int timeout = jtaAnnotation.timeout();
        if (timeout >= 0) {
            return new JTAJobRunShell(scheduler, bundle, timeout);
        } else {
            return new JTAJobRunShell(scheduler, bundle);
        }
    }
}

然后调用JobRunShell.initialize(qs)方法,初始化待运行的Job

public void initialize(QuartzScheduler sched)
    throws SchedulerException {
    this.qs = sched;
    Job job = null;
    JobDetail jobDetail = firedTriggerBundle.getJobDetail();
    try {
        // 这里调用QuartzScheduler的jobFactory的newJob()方法
        // 默认值(private JobFactory jobFactory = new PropertySettingJobFactory())
        job = sched.getJobFactory().newJob(firedTriggerBundle, scheduler);
    } catch (SchedulerException se) {
        ....
    } catch (Throwable ncdfe) {
        ....
    }
    // 这里初始化JobExecutionContextImpl对象jec,传入scheduler,trigger和job信息
    // 注意这个对象可以被实际执行定时任务的Job拿到
    this.jec = new JobExecutionContextImpl(scheduler, firedTriggerBundle, job);
}

PropertySettingJobFactory.newJob()方法其实就是调用反射来实例化一个JobClass的对象

public Job newJob(TriggerFiredBundle bundle, Scheduler Scheduler) throws SchedulerException {
    JobDetail jobDetail = bundle.getJobDetail();
    Class<? extends Job> jobClass = jobDetail.getJobClass();
    try {
        ....
        return jobClass.newInstance();
    } catch (Exception e) {
		....
    }
}

初始化JobRunShell对象后,就是把这个对象丢入SimpleThreadPool中执行

public void run() {
    qs.addInternalSchedulerListener(this);
    try {
        OperableTrigger trigger = (OperableTrigger) jec.getTrigger();
        JobDetail jobDetail = jec.getJobDetail();
        do {
            Job job = jec.getJobInstance();
            try {
                begin();
            } catch (SchedulerException se) {
                ....
            }
            ....
            try {
                // 所有定时任务要求实现Job接口,是因为实际执行的是Job.execute()方法
                job.execute(jec);
            } catch (JobExecutionException jee) {
                ....
            } catch (Throwable e) {
                ....
            }
			// 执行完毕后更新数据库的trigger状态,删除qrtz_fired_triggers表的数据
            ....
            qs.notifyJobStoreJobComplete(trigger, jobDetail, instCode);
            // 正常情况下是break跳出循环了,只有的需要重新执行的时候,走不到break
            break;
        } while (true);
    } finally {
        qs.removeInternalSchedulerListener(this);
    }
}

我简单写了一个定时任务,可以看到execute方法可以拿到JobExecutionContext对象 JobExecutionContext对象持有了trigger、job和scheduler的详细信息