Spring quartz中job相关理解实践

1,903 阅读8分钟

1.job

首先quartz中有三个重要的模块,定时任务Job、触发器Trigger、调度器Scheduler。

其中job是在触发器Trigger触发之后,经调度器Scheduler分配之后执行的任务。

2.预定义Job接口

预定义的job接口中,只有execute一个方法。

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public interface Job {
    void execute(JobExecutionContext var1) throws JobExecutionException;
}

3.模板方法模式

3.1 什么是模板方法模式

在抽象类中定义一个操作中的算法骨架,并且把一些步骤延迟到子类。

模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤

3.2 结构图

3.3 基本代码实现

//抽象类
public abstract class AbstractClass {

    public abstract void primitiveOperation1();

    public abstract void primitiveOperation2();

    // 模板方法,给出了逻辑的骨架
    // 而逻辑的组成是一些相应的抽象操作,他们都推迟到子类实现
    public void templateMethod() {

        primitiveOperation1();
        primitiveOperation2();
        System.out.println("");
    }
}

//具体实现类

```java
public class ConcreteClassA extends AbstractClass {

    @Override
    public void primitiveOperation1() {

        System.out.println("具体类A方法1实现");
    }

    @Override
    public void primitiveOperation2() {

        System.out.println("具体类A方法2实现");
    }
}

3.4 特点

  • 通过父类调用子类实现的方法(不变的方法),扩展性提高(子类分别实现不同算法细节),降低子类中的重复代码。
  • 主要业务场景:
    • 多个子类有共有的方法,并且逻辑基本相同。
    • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
    • 重构时,模板方法是一个经常使用的方法,把相同的代码抽取到父类中,然后通过构造函数约束其行为

4.基于模板方法模式的设计实现

4.1 为什么要用模板方法模式

业务场景:
在实际生产中,不同的功能性的定时任务是写在不同的Job中,他们通过实现Job接口,并且重写execute方法实现不同定时任务的逻辑。

public class QuartzHelloJob implements Job {
 
    private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
 
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(format.format(new Date()) + " Hello Quartz!");
    }
}

但是大部分情况下,每个定时任务都有一段公共代码,如触发处理jobDetail的逻辑等,所以这里定义一个抽象Job类,即BaseJob类来屏蔽execute方法,并且直接在execute方法中调用一个新的doJob()方法。这样的话
1.能够提供一些通用的功能逻辑写再execute()方法中,方便扩展
2.屏蔽了 JobExecutionContext 与 JobExecutionException

4.2 设计实现

本设计是主要运用到了类的继承,在Job接口中定义execute方法,然后用抽象类BaseJob去继承,最后让实现类XXXJob去执行具体的逻辑。子类重写了父类的方法,如果子类调用该方法,运行的是子类的方法,不会运行父类该方法。

public abstract class BaseJob implements Job {
 
    private static final Logger logger = Logger.getLogger(BaseJob.class);
 
    @Override
    public final void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            dojob(XXX);
        } catch (Exception e) {
            logger.error("执行 Job 出错!", e);
        }
    }
 
    public abstract void dojob(String XXX);
}

XXXJob类实现:

  
public class testJob extends BaseJob {
    @Override
    public String doJob(String jobParameters) throws Exception {
         // 各自Job的逻辑实现
    } 
}

5.job是如何执行的

参考:blog.csdn.net/GAMEloft9/a…
blog.csdn.net/GAMEloft9/a…

5.1 基本流程

1. 创建SchedulerFactory
2. 创建Scheduler
3. 创建JobDetail
4. 创建Trigger
5. 注册到Scheduler:scheduler.scheduleJob(jobDetail, trigger)
6. 启动Scheduler:scheduler.start()
 
public class RAMJobTest {

    @Test
    public void testExecute() throws SchedulerException, InterruptedException {
        // 1.创建Scheduler的工厂
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        // 2.从工厂中获取调度器实例
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 3.创建JobDetail
        JobDetail jobDetail = JobBuilder.newJob(RAMJob.class)
                .withDescription("this is a ram job") //job的描述
                .withIdentity("ramJob", "ramGroup") //job 的name和group
                .build();
        // 4.创建Trigger
        Trigger trigger = TriggerBuilder.newTrigger().withDescription("")
                .withIdentity("ramTrigger", "ramTriggerGroup")
                .startAt(new Date()) // 默认当前时间启动
                .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) // 两秒执行一次
                .build();
        // 5.注册任务和定时器
        scheduler.scheduleJob(jobDetail, trigger);
        // 6.启动调度器
        scheduler.start();
        System.out.println("启动时间 : " + new Date() + " " + Thread.currentThread().getName());
        Thread.sleep(60000);        
        System.out.println("done");
    }

} 
作者:icameisaw
链接:https://www.jianshu.com/p/3f77224ad9d4
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

5.2 创建SchedulerFactory及Scheduler

SchedulerFactory是生产Scheduler实例的工厂类

import java.util.Collection;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;

public interface SchedulerFactory {
    Scheduler getScheduler() throws SchedulerException;

    Scheduler getScheduler(String var1) throws SchedulerException;

    Collection<Scheduler> getAllSchedulers() throws SchedulerException;
}
这个接口有两个实现类:
StdSchedulerFacotory通过配置文件来设置Scheduler的各项参数
DirectSchedulerFactory主要通过硬编码来设置Scheduler的各项参数

本项目使用的是StdSchedulerFacotory:

创建Scheduler实例的流程:

www.jianshu.com/p/760a96048…

5.3 创建Jobdetail和trigger

JobDetailImpl.java

JobDetailImpl是接口JobDetail的唯一实现类,本质上来说是一个Java Bean,这里主要是要理解各个属性的意思。

public class JobDetailImpl implements Cloneable, java.io.Serializable, JobDetail {

    // Job的名称
    private String name;

    // Job的分组
    private String group = Scheduler.DEFAULT_GROUP;

    // Job的描述
    private String description;

    // 执行Job业务逻辑的对应实体类的Class引用
    private Class<? extends Job> jobClass;

    // 保存关于Job的信息,根据业务逻辑自己放进去
    private JobDataMap jobDataMap;

    // 当没有绑定Trigger的情况,是否保存Job
    private boolean durability = false;

    // Job是否可从“恢复”情况下再次执行
    private boolean shouldRecover = false;

    // 封装了name和group,作为JobDetail的唯一标识
    // 用空间来换取时间和可读性的策略
    private transient JobKey key = null;
}

JobDataMap

JobDataMap提供了一个Map<String, Object>的对象,我们可以在管理或者执行Job的过程中保存或者查询一些自定义的信息。

JobBuilder

  • 将JobDetail的属性都封装起来
  • 允许JobDeatil通过多个步骤来创建,并且可以改变过程或者顺序
//  创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(RAMJob.class)
        .withDescription("this is a ram job") //job的描述
        .withIdentity("ramJob", "ramGroup") //job 的name和group
        .build();
        
// build方法
public JobDetail build() {
    JobDetailImpl job = new JobDetailImpl();

    job.setJobClass(jobClass);
    job.setDescription(description);
    if(key == null)
        key = new JobKey(Key.createUniqueName(null), null);
    job.setKey(key);
    job.setDurability(durability);
    job.setRequestsRecovery(shouldRecover);

    if(!jobDataMap.isEmpty())
        job.setJobDataMap(jobDataMap);

    return job;
}

//jobDataMap字段属性
public class JobBuilder {
    public JobBuilder withIdentity(String name)
    public JobBuilder withIdentity(String name, String group)
    public JobBuilder withIdentity(JobKey jobKey)
    public JobBuilder withDescription(String jobDescription)
    public JobBuilder ofType(Class<? extends Job> jobClazz)
    public JobBuilder requestRecovery()
    public JobBuilder requestRecovery(boolean jobShouldRecover)
    public JobBuilder storeDurably()
    public JobBuilder storeDurably(boolean jobDurability)
    public JobBuilder usingJobData(String dataKey, String value)
    public JobBuilder usingJobData(String dataKey, Integer value)
    public JobBuilder usingJobData(String dataKey, Long value)
    public JobBuilder usingJobData(String dataKey, Float value)
    public JobBuilder usingJobData(String dataKey, Double value)
    public JobBuilder usingJobData(String dataKey, Boolean value)
    public JobBuilder usingJobData(JobDataMap newJobDataMap)
    public JobBuilder setJobData(JobDataMap newJobDataMap)
}

Trigger相关

Trigger trigger = TriggerBuilder.newTrigger().withDescription("")
    .withIdentity("ramTrigger", "ramTriggerGroup")
    .startAt(new Date()) // 默认当前时间启动
    .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) // 两秒执行一次
    .usingJobData("triggerKey", "some important information to save")
    .build();

TriggerBuilder

TriggerBuilder是一个泛型类,与JobBuilder有点不一样,而且创建Trigger实例的动作委托给了ScheduleBuilder类。ScheduleBuilder,这次不顾名思义了,它作为一个生成器,不是要生成Scheduler类,而是要生成MutableTrigger实例。

5.4 注册到Scheduler

scheduler.scheduleJob(jobDetail, trigger)

流程如下:

StdScheduler

StdScheduler的方法基本上都代理给QuartzScheduler类来处理

QuartzScheduler

org.quartz.Scheduler接口的间接实现,quartz的核心调度类,任务的调度和任务的管理都是QuartzScheduler实现的,然后通过一个静态代理类StdScheduler提供出来。

public class QuartzScheduler implements RemotableQuartzScheduler {

    // QuartzSchedulerResources对象是通过构造器放进去的
    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);
        }

        jobMgr = new ExecutingJobsManager();
        addInternalJobListener(jobMgr);
        errLogger = new ErrorLogger();
        addInternalSchedulerListener(errLogger);

        signaler = new SchedulerSignalerImpl(this, this.schedThread);

        getLog().info("Quartz Scheduler v." + getVersion() + " created.");
    }

    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");
        }
        // TriggerBuilder.build()会生成一个OperableTrigger实例。
        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());
        }
        // TODO: 解析各种类型的Trigger
        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;
    }

    // 其他代码

}

scheduler.start()

public class QuartzScheduler implements RemotableQuartzScheduler {

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

        if (initialStart == null) {//初始化标识为null,进行初始化操作
            initialStart = new Date();
            // RAMJobStore 啥都不做
            // JobStoreSupport 判断是否集群,恢复Job等
            this.resources.getJobStore().schedulerStarted();           
            startPlugins();
        } else {
            resources.getJobStore().schedulerResumed();// 如果已经初始化过,则恢复jobStore
        }

        schedThread.togglePause(false);// 唤醒所有等待的线程

        getLog().info("Scheduler " + resources.getUniqueIdentifier() + " started.");

        notifySchedulerListenersStarted();
    }

    // 其他代码 
}

QuartzSchedulerThread

public class QuartzSchedulerThread extends Thread {

    /**
     * pause为true,发出让主循环暂停的信号,以便线程在下一个可处理的时刻暂停
     * pause为false,唤醒sigLock对象的所有等待队列的线程
     */
    void togglePause(boolean pause) {
        synchronized (sigLock) {
            paused = pause;

            if (paused) {
                signalSchedulingChange(0);
            } else {
                sigLock.notifyAll();
            }
        }
    }

    // 其他代码

}

listener事件监听

Listener事件监听是观察者模式的一个应用

QuartzScheduler的scheduleJob()start()方法都有notifyXXX代码逻辑,这些就是JobDetail、Trigger和Scheduler事件监听的代码逻辑。
在《Scheduler的初始化》篇章里面,初始化一个Scheduler,里面有"根据PropertiesParser创建Listeners"的步骤,**Listeners就包括JobListener和TriggerListener的List对象**。
SchedulerListener不支持配置在quartz.properties里面,初始化Scheduler的过程中没有这一块的代码逻辑。如果要添加一个观察者,那么可以**通过StdScheduler.getListenerManager()获取ListenerManager实例**,通过它可以拿到所有观察者的引用。
 

Subject通知Observer,都是遍历Observer列表,触发相应的通知,实现事件监听的效果。

5.5 QuartzScheduler中创建启动QuartzSchedulerThread

什么时候创建

  • StdSchedulerFactory.instantiate():生产StdScheduler过程中会new一个QuartzScheduler实例
qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
  • 在QuartzScheduler的构造器方法里面可以看到创建QuartzSchedulerThread的代码逻辑,并通过QuartzSchedulerResources对象获取ThreadExecutor对象,最后execute新建的QuartzSchedulerThread。
// QuartzSchedulerThread创建和启动的逻辑
this.schedThread = new QuartzSchedulerThread(this, resources);
ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
schedThreadExecutor.execute(this.schedThread);
  • DefaultThreadExecutor是ThreadExecutor接口的唯一实现类,传入指定的Thread对象,便启动该线程。到这里,QuartzSchedulerThread启动了。
public class DefaultThreadExecutor implements ThreadExecutor {

    public void initialize() {
    }

    public void execute(Thread thread) {
        thread.start();
    }

}

QuartzSchedulerThread中run()方法

  • 创建JobRunShell
  • ThreadPool.runInThread()
  • WorkerThread.run(runnable) (WorkerThread的初始化):
    • StdSchedulerFactory.instantiate()创建了ThreadPool tp
    • tp.initialize()里面有初始化WorkerThread的逻辑
  • WorkerThread.run()
  • JobRunShell.run()

5.6 Job执行状态

STATE_BLOCKED 4 阻塞 STATE_COMPLETE 2 完成 STATE_ERROR 3 错误 STATE_NONE -1 不存在 STATE_NORMAL 0 正常 STATE_PAUSED 1 暂停

Scheduler scheduler = schedulerFactoryBean.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
TriggerState state = scheduler.getTriggerState(triggerKey);

n.总结

  • 设计模式的学习最好能结合业务场景同时理解。

  • @Component这个注解的作用标记这个类可以被IOC容器管理,类上加上此标记可让Spring扫描到。

  • 参考:www.jianshu.com/p/38e5e0953…