Quartz(四) 整合springboot、动态定时任务、监听器

一 SchedulerFactoryBean

SchedulerFactoryBean这个类的真正作用提供了对org.quartz.Scheduler的创建与配置,并且会管理它的生命周期Spring同步
org.quartz.Scheduler: 调度器。所有的调度都是由它控制
该类可以帮助我们设置Scheduler的一些属性

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory) throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        //可选,QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
        factory.setOverwriteExistingJobs(true);
        factory.setAutoStartup(true); //设置自行启动
        factory.setDataSource(dataSource);
        factory.setJobFactory(jobFactory);
        factory.setQuartzProperties(quartzProperties());
        return factory;
    }

注入Scheduler


    @Bean(name = "scheduler")
    public Scheduler scheduler() {
        return schedulerFactoryBean().getScheduler();
    }

任务工厂JobFactory

job实例都是由quartzJobFactory创建的,默认情况下我们在job实现类中注入spring对象都是无效的。因为对象并没有被spring纳入管理。
这时我可以通过AutowireCapableBeanFactory来帮助我们实现

@Component
public class JobFactory extends AdaptableJobFactory {
    /**
     * AutowireCapableBeanFactory接口是BeanFactory的子类,可以连接和填充那些生命周期不被Spring管理的已存在的bean实例
     */
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;
 
    /**
     * 创建Job实例
     * @param bundle
     * @return
     * @throws Exception
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 实例化对象
        Object jobInstance = super.createJobInstance(bundle);
        // 进行注入(Spring管理该Bean)
        capableBeanFactory.autowireBean(jobInstance);
        //返回对象
        return jobInstance;
    }
}

方式二
本质都是借助AutowireCapableBeanFactory来实现

//配置JobFactory,为quartz作业添加自动连接支持
    public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
            ApplicationContextAware {
        private transient AutowireCapableBeanFactory beanFactory;
        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            beanFactory.autowireBean(job);
            return job;
        }
    }

读取quartz.properties

    //从quartz.properties文件中读取Quartz配置属性
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }


然后factory.setQuartzProperties(quartzProperties());

其他配置

        //可选,QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
        factory.setOverwriteExistingJobs(true);
        factory.setAutoStartup(true); //设置自行启动
        factory.setDataSource(dataSource);

setOverwriteExistingJobs:设置是否任意一个已定义的Job会覆盖现在的Job。默认为false,即已定义的Job不会覆盖现有的Job。(用于集群)
setAutoStartup:设置自行启动
setDataSource:设置数据源,使用与项目统一数据源
setStartupDelay:项目启动完成后,等待x秒后开始执行调度器初始化

二 动态定时任务

TriggerState

Trigger.TriggerState.NONE

  • STATE_BLOCKED 4 阻塞
  • STATE_COMPLETE 2 完成
  • STATE_ERROR 3 错误
  • STATE_NONE -1 不存在
  • STATE_NORMAL 0 正常
  • STATE_PAUSED 1 暂停
TriggerKey triggerKey = new TriggerKey(name, group);
scheduler.getTriggerState(triggerKey).name())

Scheduler

Scheduler的创建方式

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
 
DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
Scheduler scheduler1=factory.getScheduler();

常用方法

  • 添加一个定时任务:
    Date scheduleJob(JobDetail jobDetail,Trigger trigger)
  • 修改一个定时任务,主要是更改trigger:
    Date rescheduleJob(String triggerName, String groupName, Trigger newTrigger)
  • 删除一个定时任务,同时也会将于该jobDetail关联的trigger一并删除:
    boolean deleteJob(String jobName,String jobGroup)
  • 取得所有的jobDetail组
    String[] getJobGroupNames()
  • 取得某个group下的所有的jobDetail
    String[] getJobNames(String groupName)
  • 取得指定的jobDetail
    JobDetail getJobDetail(String jobName, String jobGroup)
  • 取得指定的jobDetail的所有的Trigger
    Trigger[] getTriggersOfJob(String jobName, String groupName)
  • 取得指定的Trigger
    Trigger getTrigger(String triggerName, String triggerGroup)

rescheduleJob

Trigger trigger = newTrigger()
    .withIdentity("newTrigger", "group1")
    .startNow()
    .build();

// tell the scheduler to remove the old trigger with the given key, and put the new one in its place
sched.rescheduleJob(triggerKey("oldTrigger", "group1"), trigger);

注意:sched.rescheduleJob(triggerKey("oldTrigger", "group1"), trigger); 这个方法返回一个Date.
如果返回 null说明替换失败,原因就是旧触发器没有找到,所以新的触发器也不会设置进去
替换失败的原因一般有两种

  • 一种情况是传入的triggerKey没有与之匹配的,
  • 另外一种情况就是旧触发器的触发时间已经全部完成,在触发完成后调度引擎会自动清除无用的触发器,这种情况也会匹配不到。

pauseAll

暂停所有任务

scheduler.pauseAll();

pauseJob

暂停某个任务

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            return;
        }
        scheduler.pauseJob(jobKey);

resumeAll

恢复所有任务

 scheduler.resumeAll();

resumeJob

恢复某个任务

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            return;
        }
        scheduler.resumeJob(jobKey);

deleteJob

删除某个任务

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            return;
        }
        scheduler.deleteJob(jobKey);

deleteJob

移除一个任务

            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));

GroupMatcher

分组匹配

        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);

三 监听器

分类

  • JobListener
  • TriggerListener
  • SchedulerListener

概念

  • 全局监听器
  • 非全局监听器(只能接收到在其上注册的Job或Trigger的事件,不在其上注册的Job或Trigger则不会进行监听)

JobListener

public interface JobListener {

    public String getName();

    public void jobToBeExecuted(JobExecutionContext context);

    public void jobExecutionVetoed(JobExecutionContext context);

    public void jobWasExecuted(JobExecutionContext context,JobExecutionException jobException);

}
  1. getName方法:用于获取该JobListener的名称
  2. jobToBeExecuted方法:Scheduler在JobDetail将要被执行时调用这个方法。
  3. jobExecutionVetoed方法:(这个方法正常情况下不执行,但是如果当TriggerListener中的vetoJobExecution方法返回true时,那么执行这个方法; 需要注意的是 如果方法(23执行 那么(2),(4)这个俩个方法不会执行,因为任务被终止了嘛.)
  4. jobWasExecuted方法:Scheduler在JobDetail被 执行之后调用这个方法

全局监听

        // 创建并注册一个全局的Job Listener
        scheduler.getListenerManager().addJobListener(new SimpleJobListener(), EverythingMatcher.allJobs());

局部监听

        // 创建并注册一个指定任务的Job Listener
        scheduler.getListenerManager().addJobListener(new SimpleJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("HelloWorld1_Job", "HelloWorld1_Group")));

将同一任务组的任务注册到监听器中

scheduler.getListenerManager().addJobListener(new SimpleJobListener(), GroupMatcher.jobGroupEquals("HelloWorld2_Group"));

将两个任务组的任务注册到监听器中

scheduler.getListenerManager().addJobListener(new SimpleJobListener(), OrMatcher.or(GroupMatcher.jobGroupEquals("HelloWorld1_Group"), GroupMatcher.jobGroupEquals("HelloWorld2_Group")));

TriggerListener

任务调度过程中,与触发器Trigger相关的事件包括:触发器触发、触发器未正常触发、触发器完成等。TriggerListener的接口如下:

public interface TriggerListener {

    public String getName();

    public void triggerFired(Trigger trigger, JobExecutionContext context);

    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

    public void triggerMisfired(Trigger trigger);

    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            int triggerInstructionCode);
}
  1. getName方法:用于获取触发器的名称
  2. triggerFired方法:当与监听器相关联的Trigger被触发,Job上的execute()方法被执行时,Scheduler就调用该方法。
  3. vetoJobExecution方法:在 Trigger 触发后,Job 要被执行时由 Scheduler 调用这个方法。TriggerListener 给了一个选择否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行
  4. triggerMisfired方法:Scheduler 调用这个方法是在 Trigger 错过触发时。你应该关注此方法中持续时间长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当保持这上方法尽量的小。
  5. triggerComplete方法:Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。

简单实现

package org.ws.quartz.test3;

import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.Trigger.CompletedExecutionInstruction;
import org.quartz.TriggerListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleTriggerListener implements TriggerListener{
    
    private static Logger logger = LoggerFactory.getLogger(SimpleTriggerListener.class);
    
    private String name;
    
    public SimpleTriggerListener(String name) {
        this.name = name;
    }
    
    @Override
    public String getName() {
        return name;
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        String triggerName = trigger.getKey().getName();
        logger.info(triggerName + " was fired");
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        String triggerName = trigger.getKey().getName();
        logger.info(triggerName + " was not vetoed");
        return false;
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        String triggerName = trigger.getKey().getName();
        logger.info(triggerName + " misfired");
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode) {
        String triggerName = trigger.getKey().getName();
        logger.info(triggerName + " is complete");
    }
}

各种注册

        // 创建并注册一个全局的Trigger Listener
        scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), EverythingMatcher.allTriggers());
        
        // 创建并注册一个局部的Trigger Listener
//        scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), KeyMatcher.keyEquals(TriggerKey.triggerKey("HelloWord1_Job", "HelloWorld1_Group")));
        
        // 创建并注册一个特定组的Trigger Listener
//        scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), GroupMatcher.groupEquals("HelloWorld1_Group"));

SchedulerListener

SchedulerListener会在Scheduler的生命周期中关键事件发生时被调用。与Scheduler有关的事件包括:增加一个job/trigger,删除一个job/trigger,scheduler发生严重错误,关闭scheduler等。

public interface SchedulerListener {

    public void jobScheduled(Trigger trigger);

    public void jobUnscheduled(String triggerName, String triggerGroup);

    public void triggerFinalized(Trigger trigger);

    public void triggersPaused(String triggerName, String triggerGroup);

    public void triggersResumed(String triggerName, String triggerGroup);

    public void jobsPaused(String jobName, String jobGroup);

    public void jobsResumed(String jobName, String jobGroup);

    public void schedulerError(String msg, SchedulerException cause);

    public void schedulerStarted();

    public void schedulerInStandbyMode();

    public void schedulerShutdown();

    public void schedulingDataCleared();
}
  1. jobScheduled方法:用于部署JobDetail时调用
  2. jobUnscheduled方法:用于卸载JobDetail时调用
  3. triggerFinalized方法:当一个 Trigger 来到了再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移除。
  4. triggersPaused方法:Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话,triggerName 参数将为 null。
  5. triggersResumed方法:Scheduler 调用这个方法是发生成一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话,假如是 Trigger 组的话,triggerName 参数将为 null。参数将为 null。
  6. jobsPaused方法:当一个或一组 JobDetail 暂停时调用这个方法。
  7. jobsResumed方法:当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。
  8. schedulerError方法:在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。
  9. schedulerStarted方法:当Scheduler 开启时,调用该方法
  10. schedulerInStandbyMode方法: 当Scheduler处于StandBy模式时,调用该方法
  11. schedulerShutdown方法:当Scheduler停止时,调用该方法
  12. schedulingDataCleared方法:当Scheduler中的数据被清除时,调用该方法。

注册于注销

        // 创建SchedulerListener
        scheduler.getListenerManager().addSchedulerListener(new SimpleSchedulerListener());
        
        // 移除对应的SchedulerListener
//        scheduler.getListenerManager().removeSchedulerListener(new SimpleSchedulerListener());

参考

  1. SpringBoot整合Quartz作为调度中心完整实用例子
  2. SpringBoot集成Quartz动态定时任务(解决quartz的job无法注入spring对象的问题)
  3. spring boot2 整合(四)定时任务Scheduled || Quartz并持久化
  4. http://blog.sina.com.cn/s/blog_45411cf30100po3j.html
  5. Quartz Scheduler 更新任务触发器
  6. Quartz使用(4) - Quartz监听器Listerner
  7. Quartz JobListener 任务监听器
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355