详解Java Quartz Job Scheduling

Quartz Job Scheduling是基于Java实现的成熟的企业级作业调度组件。笔者最近所做的项目正好用到了Quartz来实现定时任务的调度,在使用过程中对Quartz不甚了解,于是趁此闲暇机会,学习了Quartz官方教程和《Quartz Job Scheduling Framework》。并形成此篇详解Java Quartz Job Scheduling。

一、 Hello Quartz

本节通过一个Hello Quartz的示例,来介绍Quartz中的核心概念。这个示例先打印”Hello Quartz!”,再以10秒频率打印当前系统时间:

package com.quartz.learning;

import org.quartz.*;

/**
 * HelloJob是一个简单的job,用于打印指定内容
 *
 * Created by zhuyiquan90 on 2018/8/18.
 */
public class HelloJob implements Job{
   

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // JobDetail
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        // JobDataMap
        JobDataMap dataMap = jobDetail.getJobDataMap();
        String content = dataMap.getString("CONTENT");
        System.out.println(content);
    }
}
package com.quartz.learning;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * HelloQuartz是一个简单的Quartz调度器
 * <p>
 * Created by zhuyiquan90 on 2018/8/18.
 */
public class HelloQuartz {
   

    private static Log logger = LogFactory.getLog(HelloQuartz.class);

    public static void main(String[] args) {

        try {
            // 从Scheduler工厂获取一个Scheduler的实例
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            scheduler.start();
            /**
             * 重用HelloJob,实现不同实例
             */
            // 注册jobDetail1,打印"Hello Quartz!",第5秒钟执行一次
            JobDetail jobDetail1 = newJob(HelloJob.class).withIdentity("job1", "group").build();
            jobDetail1.getJobDataMap().put("CONTENT", "Hello Quartz!");
            Trigger trigger1 = newTrigger().withIdentity("trigger1", "group").startNow()
                    .withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(0)).build();
            scheduler.scheduleJob(jobDetail1, trigger1);

            // 注册jobDetail2,打印当前系统时间,每10秒钟执行一次
            JobDetail jobDetail2 = newJob(HelloJob.class).withIdentity("job2", "group").build();
            jobDetail2.getJobDataMap().put("CONTENT", String.valueOf(System.currentTimeMillis()));
            Trigger trigger2 = newTrigger().withIdentity("trigger2", "group").startNow()
                    .withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();
            scheduler.scheduleJob(jobDetail2, trigger2);

        } catch (SchedulerException e) {
            logger.error(e);
        }
    }
}

输出结果如下:

19:11:08.473 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group.job1
19:11:08.473 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
Hello Quartz!
19:11:08.473 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job group.job2
1534590668464
19:11:18.468 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group.job2', class=com.quartz.learning.HelloJob
19:11:18.469 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
19:11:18.469 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job group.job2
1534590668464
19:11:28.468 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group.job2', class=com.quartz.learning.HelloJob
19:11:28.469 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.core.JobRunShell - Calling execute on job group.job2
1534590668464

Scheduler(调度器)是Quartz框架的心脏。Scheduler的生命周期始于通过SchedulerFactory工厂类创建实例,终于调用shutdown() 方法。Scheduler不仅可以用于新增、移除、列举Jobs和Triggers,还可以执行调度相关操作,比如暂停Trigger、恢复Trigger等。需要注意的是,直到调用start()方法时,Scheduler才正式开始执行job和trigger。
Job(作业)是指执行一些作业的特定的Java类。Job必须实现 org.quartz.Job接口,这个接口要求在Job中实现execute()方法。当 Quartz 调用 execute() 方法,会传递一个 JobExecutionContext 上下文变量,里面封装有 Quartz 的运行时环境和当前正执行的 Job。JobExecutionContext可以被用来访问 JobDetail 类,JobDetail 类持有Job的详细信息,包括为Job实例指定的名称,Job 所属组,Job 是否被持久化(易失性)。JobDetail又持有一个指向JobDataMap的引用。JobDataMap中包含Job配置的自定义属性。
Trigger(触发器)用于触发Job的执行。最常用的类型包括 SimpleTriggerCronTrigger
下面针对Quartz的核心框架展开详述。

二、Quartz框架核心

2.1 Scheduler

客服端与Scheduler交互是通过org.quartz.Scheduler接口的。这个 Scheduler 的实现,在这种情况下,是一个代理,对其中方法调用会传递到QuartzScheduler实例上。QuartzScheduler对于客户端是不可见的,并且也不存在与此实例的直接交互。QuartzScheduler处在框架根的位置,它是一个引擎驱动着整个框架。
Scheduler主要包括StdScheduler(Quartz默认的Scheduler)和RemoteScheduler(带有RMI功能的Scheduler)。
Quartz提供了org.quartz.SchedulerFactory接口来创建Scheduler实例。SchedulerFactory包括两种类型org.quartz.impl.DirectoSchedulerFactoryorg.quartz.impl.StdSchedulerFactory
这里写图片描述

2.1.1 使用DirectSchedulerFactory创建实例

DirectSchedulerFactory通过编程式的方式来创建Scheduler实例。一般包括三个基本步骤。首先,你必须用静态方法 getInstance() 获取到工厂的实例。当你持有了工厂的实例之后,你必须调用其中的一个 createXXX 方法去初始化它。第三步也就是最后一步是通过工厂的 getScheduler() 方法拿到 Scheduler 的实例。代码实例如下:

DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
try {
    // Initialize the Scheduler Factory with 10 threads
    factory.createVolatileScheduler(10);

    // Get a scheduler from the factory
    Scheduler scheduler = factory.getScheduler();

    // Start the scheduler running
    logger.info("Scheduler starting up...");
    scheduler.start();

    // Do something
    ...
 } catch (SchedulerException e) {
    logger.error(e);
}

采用编程式来创建实例你需要硬编码所有的scheduler配置,这无疑是很有挑战性的。所以请慎用DirectoSchedulerFactory

2.1.2 使用StdSchedulerFactory创建实例

StdSchedulerFactory通过声明式的方式来创建Scheduler实例。它依赖于一系列的属性配置。比如

Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

就是以默认的配置文件quartz.properties来实例化scheduler。一个简单的quartz.properties配置如下所示。Quartz配置参考详见第七节。

#===============================================================     
#Configure Main Scheduler Properties     
#===============================================================      
org.quartz.scheduler.instanceName = QuartzScheduler      
org.quartz.scheduler.instanceId = AUTO     

#===============================================================     
#Configure ThreadPool     
#===============================================================      
org.quartz.threadPool.threadCount =  5      
org.quartz.threadPool.threadPriority = 5      
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool     

#===============================================================     
#Configure JobStore     
#===============================================================      
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore     

#===============================================================     
#Configure Plugins     
#===============================================================      
org.quartz.plugin.jobInitializer.class =      
org.quartz.plugins.xml.JobInitializationPlugin      

org.quartz.plugin.jobInitializer.overWriteExistingJobs = true     
org.quartz.plugin.jobInitializer.failOnFileNotFound = true     
org.quartz.plugin.jobInitializer.validating=false  

2.1.3 管理 Scheduler实例

  • 启动Scheduler

启动Scheduler通过start()

//Create an instance of the Scheduler  
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();  

//Start the scheduler  
scheduler.start();  
  • 临时暂停Scheduler

临时暂停调度的方式分为standBy()pauseAll()两种。两者的区别如下。

standby()

Temporarily halts the Scheduler’s firing of Triggers.
void standby() throws SchedulerException Temporarily halts the Scheduler’s firing of Triggers.

When start() is called (to bring the scheduler out of stand-by mode),
trigger misfire instructions will NOT be applied during the execution
of the start() method - any misfires will be detected immediately
afterward (by the JobStore’s normal process).

The scheduler is not destroyed, and can be re-started at any time.

pauseAll()

void pauseAll() throws SchedulerException Pause all triggers - similar to calling
pauseTriggerGroup(group) on every group, however, after using this
method resumeAll() must be called to clear the scheduler’s state of
‘remembering’ that all new triggers will be paused as they are added.

When resumeAll() is called (to un-pause), trigger misfire instructions WILL be applied.

说明standby()需要再次调用start()恢复调度,trigger misfire(触发未执行调度)策略在start()执行过程中将不会直接执行;pauseAll()需要调用resumeAll()恢复调度,所有trigger misfire策略将被立即执行。

  • 终止Scheduler

终止调度的方式是shutdown()

public void shutdown(boolean waitForJobsToComplete)  throws SchedulerException;  

public void shutdown() throws SchedulerException;  

上面那两个方法唯一不同之处是其中一个方法可接受一个 boolean 型参数,表示是否让当前正在进行的 Job 正常执行完成才停止 Scheduler。无参的 shutdown() 方法相当于调用 shutdown(false)。
以上,start(),standBy(),pauseAll(),shutdown()等原子操作都是 QuartzScheduler完成的,后续会有专门文章对QuartzScheduler(Quartz框架的根本)进行源码剖析。

2.2 Job

如第一节所述,HelloJob引用了 org.quartz.Job 接口,并实现了execute() 方法。Scheduler决定运行HelloJob的时机,JobExecutionContext用于记录Job上下文,execute()执行异常抛出JobExecutionException
Job如何注册到Scheduler中?如何定义Job实例的属性和配置?执行过程中如何追踪Job的状态?下面还有一些我们必须了解的关于Job的特征。

2.2.1 JobDetail

从第一节的例子,我们可以看到不是直接把Job对象注册到Scheduler,实际注册的是一个JobDetail实例。这样做的好处是,针对某一类的Job,仅需要构造一个Job class,比如文件操作类Job,通过创建多个JobDetail实例来完成不同的调度任务。
JobDetail实例通过org.quartz.JobBuilder构造。我们将JobDetail注册到Scheduler,通过newJob(),Scheduler知道所要执行的具体Job。每一次Scheduler执行,在调用execute()方法前,会创建一个新的实例。当执行完毕,相关的job实例会被丢弃,对应的堆内存会被回收。换句话说,Job是无状态的(在最新的版本中,StatefulJob已经废弃)。因此我们需要使用JobDataMap来传递数据。

2.2.2 JobDataMap

我们能使用 org.quartz.JobDataMap 来定义 Job 的状态。JobDataMap 是JobDetail的一部分。可以向 JobDataMap 中存入键/值对,那些数据对可在你的 Job 类中传递和进行访问。
Trigger中也可以使用JobDataMap,这在一个Job应用于多个Triggers的场景下非常使用于参数传递。最终JobExecutionContext上下文中传递的JobDataMap是JobDetail和Trigger的并集,通过getMergedJobDataMap()获取。
注意,对同一key,如果在JobDetail和Trigger中都有使用,后来者会覆盖先来者。如下面的例子:

public class HelloJob implements Job{
   

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDataMap dataMap = jobExecutionContext.getMergedJobDataMap();;
        String content = dataMap.getString("CONTENT");
        System.out.println(content);
    }
}
public class HelloQuartz {
   

    private static Log logger = LogFactory.getLog(HelloQuartz.class);

    public static 
  • 12
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值