Spring quartz中job相关理解实践

1.job

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

其中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 什么是模板方法模式

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

模板方法使得子类能够不改变一个算法的结构便可从新定义该算法的某些特定步骤ide

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去执行具体的逻辑。子类重写了父类的方法,若是子类调用该方法,运行的是子类的方法,不会运行父类该方法。ui

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类实现:this

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…

相关文章
相关标签/搜索