SpringBoot2.0整合quartz

前言

在SpringBoot中,自身提供了@Scheduled等注解实现简单的定时任务,但在面对较复杂的定时需求时,仍然需要使用quartz,即一有定时处理的需求,应该优先使用quartz来处理。

下面,将介绍在SpringBoot2.0中如何整合quartz,免去以前那种繁杂的xml配置方式,不涉及数据库保存定时记录等复杂内容。

SpringBoot版本

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.0.4.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

一、开始添加定时任务前的配置

1、在pom.xml中引入依赖

<!--spring boot2.x以前quartz相关依赖-->
<!--<dependency>
   <groupId>org.quartz-scheduler</groupId>
   <artifactId>quartz</artifactId>
   <version>${quartz.version}</version>
</dependency>
<dependency>
   <groupId>org.quartz-scheduler</groupId>
   <artifactId>quartz-jobs</artifactId>
   <version>${quartz.version}</version>
</dependency>-->

<!--spring boot2.x后quartz依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2、添加quartz的properties配置

在resources下,新建application-quartz.yml,内容如下

#quartz相关属性配置
spring:
  quartz:
    properties:
      org:
        quartz:
          scheduler:
            #scheduler实例名称与id分配
            instanceName: clusteredScheduler
            instanceId: AUTO
            #job的保存设置
#          jobStore:
#            class: org.quartz.impl.jdbcjobstore.JobStoreTX
#            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#            tablePrefix: QRTZ_
#            isClustered: true
#            clusterCheckinInterval: 10000
#            useProperties: false
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
    #数据库方式,默认使用memory的方式(此时不需要配置jobStore)
#    job-store-type: jdbc
    #初始化表结构
#    jdbc:
#      initialize-schema: never

在本例子中,使用memory的方式保存定时任务,所以不需要配置jobStore,其他配置意思见注释

 

3、配置自定义的Scheduler

新建类‘SchedulerCustomizer’,继承‘SchedulerFactoryBeanCustomizer’

/**
* @date 2019/3/3
* @des 配置自定义的Scheduler;注意,若在此设定scheduler延时启动N秒,
*      则job的启动时间(包括延时启动)便要大于或等于这个延时时间N
*/
@Configuration
@EnableScheduling
public class SchedulerCustomizer implements SchedulerFactoryBeanCustomizer{


    @Override
    public void customize(SchedulerFactoryBean schedulerFactoryBean) {
        schedulerFactoryBean.setStartupDelay(5);//程序启动后5秒启动定时scheduler,注意这里的设置
        schedulerFactoryBean.setAutoStartup(true);//job自启动
        schedulerFactoryBean.setOverwriteExistingJobs(true);//覆盖已存在的job
    }
}

接下来,便可以添加定时任务,进行测试啦。

定时任务的添加分为动态与静态两种,下面分别介绍。

 

二、添加定时任务

A、静态添加

新建一个类‘QuartzConfiguration’,如

/**
* @des 第1种创建job任务的方式,即通过配置类;每一个job都需要在此配值,提供两个bean,一个JobDetail,一个Trigger
*/
@Configuration
public class QuartzConfiguration {

}

使用SimpleSchedule

1、新建一个job,如

/**
* @des quartz 简单测试类
*/
@Slf4j
public class FirstJob extends QuartzJobBean{

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String param = jobExecutionContext.getJobDetail().getJobDataMap().getString("first");
        log.info("--FirstJob--:{} --" + LocalDateTime.now(), param);
    }
}

2、在‘QuartzConfiguration’中配置相应的bean,如

/**
* @des 每一个job都需要在此配值,提供两个bean,一个JobDetail,一个Trigger
*/
@Configuration
public class QuartzConfiguration {

    /*---使用simpleSchedule来添加任务---*/
    // 使用jobDetail包装job
    @Bean
    public JobDetail firstJobDetail() {
        //创建任务
        JobDetail jobDetail = JobBuilder.newJob(FirstJob.class).withIdentity("firstJob").storeDurably().build();

        //通过任务传递参数
        jobDetail.getJobDataMap().put("first", "hello world!");

        return jobDetail;
    }


    // 把jobDetail注册到trigger上去
    @Bean
    public Trigger firstJobTrigger() {
        //创建自己的任务调度器
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(30).repeatForever();
        //创建任务触发器
        return TriggerBuilder.newTrigger()
                .forJob(firstJobDetail())
                .withIdentity("firstJobTrigger")
                .withSchedule(scheduleBuilder) //将触发器与任务绑定到调度器内
                //.startNow() //使用startNow()马上启动不生效,因为scheduler还没启动
                //.startAt(new Date())//使用startAt(new Date())也不能生效,因为scheduler还没启动
                //.startAt(new Date(System.currentTimeMillis()))//也不生效,因为scheduler还没启动
                .startAt(new Date(System.currentTimeMillis() + 5000))//只有延时时间大于等于scheduler延时启动时间才行
                .build();
    }
}

在这里,涉及job启动时间的疑问,即‘设置job启动时间时,存在不生效的问题’

原因是

1.由于在SchedulerCustomizer设置了scheduler延时启动,所以scheduler并不是跟随程序同步启动的

2.在job中设置的延时时间是从程序启动后开始算的,而不是scheduler启动后再重新算的延时时间

3.设置startNow()不生效的原因,是因为scheduler还没到启动时间(即scheduler此时还没启动),所以无效

4.也就是说,要使job跟scheduler同步启动,则job需要设置延时启动,而且时间大于或等于scheduler的延时启动时间

  • 当‘SchedulerCustomizer’开启了延时启动后,要使job跟随scheduler马上启动或第一次延时启动,则需要设置job的启动时间,且该启动时间必须是在scheduler启动后才行,否则,只有等到job的下一次定时到达后,才执行定时任务。

  • 当‘SchedulerCustomizer’没设置延时启动,则job的启动时间不受限制

 

这时,运行程序,打印日志如下

2019-03-03 17:15:39.246  INFO 1508 --- [uartzScheduler]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 5 seconds
2019-03-03 17:15:39.247  INFO 1508 --- [uartzScheduler]] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2019-03-03 17:15:39.274  INFO 1508 --- [eduler_Worker-1] com.he.schedule.job.FirstJob             : --FirstJob--:hello world! --2019-03-03T17:15:39.274

可见 Scheduler在程序启动5秒后启动,同时FirstJob的延时时间到,此时Scheduler也已启动,所以第一次执行定时任务,否则只能等待下一次定时到达才执行。

 

使用CronSchedule

1、新建一个job,如

/**
* @des quartz cron任务测试类
*/
@Slf4j
public class FirstCronJob extends QuartzJobBean{

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("--FirstCronJob--:{}" , LocalDateTime.now());
    }
}

2、在‘QuartzConfiguration’中配置相应的bean,如

/**
* @des 每一个job都需要在此配值,提供两个bean,一个JobDetail,一个Trigger
*/
@Configuration
public class QuartzConfiguration {

    /*---使用simpleSchedule来添加任务---*/
    // 使用jobDetail包装job
    @Bean
    public JobDetail firstJobDetail() {
        //创建任务
        JobDetail jobDetail = JobBuilder.newJob(FirstJob.class).withIdentity("firstJob").storeDurably().build();

        //通过任务传递参数
        jobDetail.getJobDataMap().put("first", "hello world!");

        return jobDetail;
    }


    // 把jobDetail注册到trigger上去
    @Bean
    public Trigger firstJobTrigger() {
        //创建自己的任务调度器
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(30).repeatForever();
        //创建任务触发器
        return TriggerBuilder.newTrigger()
                .forJob(firstJobDetail())
                .withIdentity("firstJobTrigger")
                .withSchedule(scheduleBuilder) //将触发器与任务绑定到调度器内
                //.startNow() //使用startNow()马上启动不生效,因为scheduler还没启动
                //.startAt(new Date())//使用startAt(new Date())也不能生效,因为scheduler还没启动
                //.startAt(new Date(System.currentTimeMillis()))//也不生效,因为scheduler还没启动
                .startAt(new Date(System.currentTimeMillis() + 5000))//只有延时时间大于等于scheduler延时启动时间才行
                .build();
    }


     /*---使用cron表达式来创建任务---*/
    // 使用jobDetail包装job
    @Bean
    public JobDetail firstCronJobDetail() {
        return JobBuilder.newJob(FirstCronJob.class).withIdentity("firstCronJob").storeDurably().build();
    }


    // 把jobDetail注册到Cron表达式的trigger上去
    @Bean
    public Trigger firstCronJobTrigger() {
        //创建自己的任务调度器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/30 * * * * ?");//从第0秒开始,每隔30秒执行一次

        return TriggerBuilder.newTrigger()
                .forJob(firstCronJobDetail())
                .withIdentity("firstCronJobTrigger")
                .withSchedule(cronScheduleBuilder)
                .startAt(new Date(System.currentTimeMillis() + 5000))
                .build();
    }
}

启动程序,观察日志打印如下

2019-03-03 17:40:07.177  INFO 3664 --- [           main] com.he.SpringbootlessonApplication       : Started SpringbootlessonApplication in 8.586 seconds (JVM running for 9.143)
2019-03-03 17:40:12.143  INFO 3664 --- [uartzScheduler]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 5 seconds
2019-03-03 17:40:12.143  INFO 3664 --- [uartzScheduler]] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2019-03-03 17:40:12.160  INFO 3664 --- [eduler_Worker-2] com.he.schedule.job.FirstCronJob         : --FirstCronJob--:2019-03-03T17:40:12.160
2019-03-03 17:40:12.160  INFO 3664 --- [eduler_Worker-1] com.he.schedule.job.FirstJob             : --FirstJob--:hello world! --2019-03-03T17:40:12.160

至此,静态添加job任务方式介绍完毕,这种方式比较适合伴随程序启动时启动的定时场景。

 

B、动态添加以及动态对定时任务进行修改,删除等操作

1、创建‘ScheduleManager’类,添加操作定时任务的封装代码(封装得比较粗犷)

/**
 * @date 2019/3/3
 * @des schedule任务管理类,动态实现开始,暂停,停止等操作
 */
@Slf4j
@Component
public class ScheduleManager {

    @Autowired
    private Scheduler scheduler;

    /**
     * 使用SimpleScheduleBuilder创建一个定时任务
     * @param jobName job名称
     * @param jobGroupName job分组名称
     * @param triggerName 触发器名称
     * @param triggerGroupName 触发器分组名称
     * @param jobClass job的class名
     * @param durability 是否持久化
     * @param interval 间隔时间,单位秒
     * @param delay 延迟启动时间,单位秒
     * @return
     */
    public Boolean addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass,
                          boolean durability, int interval, int delay) {
        /*
         * 1.取到任务调度器Scheduler
         * 2.定义jobDetail;
         * 3.定义trigger;
         * 4.使用Scheduler添加任务;
         */
        try {
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).storeDurably(durability).build();

            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(interval).repeatForever();

            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerName, triggerGroupName)
                    .withSchedule(scheduleBuilder)
                    .startAt(new Date(new Date().getTime() + delay * 1000))
                    .build();

            scheduler.scheduleJob(jobDetail, trigger);
            return true;
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 使用CronScheduleBuilder创建一个定时任务
     *
     * @param jobName job名称
     * @param jobGroupName job分组名称
     * @param triggerName 触发器名称
     * @param triggerGroupName 触发器分组名称
     * @param jobClass job的class名
     * @param durability 是否持久化
     * @param cronExpression 定时表达式
     * @param delay 延迟启动时间,单位秒
     * @return
     */
    public Boolean addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass,
                          boolean durability, String cronExpression, int delay) {
        /*
         * 1.取到任务调度器Scheduler
         * 2.定义jobDetail;
         * 3.定义trigger;
         * 4.使用Scheduler添加任务;
         */
        try {
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).storeDurably(durability).build();

            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerName, triggerGroupName)
                    .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                    .startAt(new Date(new Date().getTime() + delay * 1000))
                    .build();

            scheduler.scheduleJob(jobDetail, trigger);
            return true;
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 动态停止job任务
     * @param jobName job名称
     * @param jobGroupName job分组名称
     * @return
     */
    public Boolean pause(String jobName, String jobGroupName) {
        try {
            JobKey key = new JobKey(jobName, jobGroupName);
            scheduler.pauseJob(key);
            return true;
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 动态开始job任务
     *
     * @param jobName job名称
     * @param jobGroupName job分组名称
     * @return
     */
    public Boolean start(String jobName, String jobGroupName) {
        try {
            JobKey key = new JobKey(jobName, jobGroupName);
            scheduler.resumeJob(key);
            return true;
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 移除定时任务
     *
     * @param jobName job名称
     * @param jobGroupName job分组名称
     * @param triggerName 触发器名称
     * @param triggerGroupName 触发器分组名称
     * @return
     */
    public Boolean delete(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        try {
            Integer status =getJobStatus(triggerName, triggerGroupName);
            if (status == 0) {
                //NONE - 0,该job不存在
                log.warn("NONE - 0,该job不存在");
                return null;
            }

            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);//通过触发器名和组名获取TriggerKey
            JobKey jobKey = new JobKey(jobName, jobGroupName); //通过任务名和组名获取JobKey
            scheduler.pauseTrigger(triggerKey);    //停止触发器
            scheduler.unscheduleJob(triggerKey);//移除触发器
            scheduler.deleteJob(jobKey);// 删除任务
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 动态修改任务执行的时间
     *
     * @param type 创建类型,1-cron,2-simple
     * @param jobName job名称
     * @param jobGroupName job分组名称
     * @param triggerName 触发器名称
     * @param triggerGroupName 触发器分组名称
     * @param time type为1时-填cron表达式,为2-整数
     * @return
     */
    public Boolean modify(String type,String jobName, String jobGroupName, String triggerName, String triggerGroupName, String time) {

        try {
            Integer status =getJobStatus(triggerName, triggerGroupName);
            if (status == 0) {
                //NONE - 0,该job不存在
                log.warn("NONE - 0,该job不存在");
                return null;
            }

            //获取任务
            JobKey key = new JobKey(jobName, jobGroupName);
            //获取jobDetail
            JobDetail jobDetail = scheduler.getJobDetail(key);
            //生成trigger
            if ("1".equals(type)) {
                // 1.CronSchedule
                Trigger trigger = TriggerBuilder
                        .newTrigger()
                        .withIdentity(triggerName, triggerGroupName)
                        .withSchedule(CronScheduleBuilder.cronSchedule(time))
                        .build();
                //删除旧的任务,否则报错
                //scheduler.deleteJob(key);
                delete(jobName, jobGroupName, triggerName, triggerGroupName);
                //重新启动任务
                scheduler.scheduleJob(jobDetail, trigger);
                return true;
            }

            if ("2".equals(type)) {
                // 2.SimpleSchedule
                Trigger trigger = TriggerBuilder
                        .newTrigger()
                        .withIdentity(triggerName, triggerGroupName)
                        .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(Integer.valueOf(time)))
                        .build();
                //删除旧的任务,否则报错
                //scheduler.deleteJob(key);
                delete(jobName, jobGroupName, triggerName, triggerGroupName);
                //重新启动任务
                scheduler.scheduleJob(jobDetail, trigger);
                return true;
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 查看job运行状态
     * @param triggerName 触发器名称
     * @param triggerGroupName 触发器分组名称
     * @return TriggerState枚举值: NONE - 0;NORMAL - 1;PAUSED - 2;COMPLETE - 3;ERROR - 4;BLOCKED - 5
     */
    public Integer getJobStatus(String triggerName, String triggerGroupName) {

        /*
            TriggerState枚举值
            NONE - 0,
            NORMAL -1,
            PAUSED - 2,
            COMPLETE - 3,
            ERROR - 4,
            BLOCKED - 5;
        */
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            return scheduler.getTriggerState(triggerKey).ordinal();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 启动所有定时任务
     */
    public Boolean startAll() {
        try {
            scheduler.start();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 关闭所有定时任务
     */
    public Boolean shutdownAll() {
        try {
            if (!scheduler.isShutdown()) {
                scheduler.shutdown();
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

}

封装的十分粗犷,有很大优化空间。

 

2、创建一个job

/**
* @des
*/
@Slf4j
public class SecondJob extends QuartzJobBean{

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
           log.info("second---: {}" , LocalTime.now());
    }
}

3、接着就在需要操作定时任务的地方,注入“ScheduleManager”进行增删改即可,eg

/**
* @des 继承ApplicationRunner,在此可进行一些程序启动后的操作,如启动一个定时任务
*      在此操作定时任务,将使得程序启动后便会执行该定时逻辑,适合伴随程序启动的定时场景
*/
@Slf4j
@Component
public class MyApplicationRunner implements ApplicationRunner {


    @Autowired
    private ScheduleManager scheduleManager;


    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        log.info("--MyApplicationRunner--");


        scheduleManager.addJob("test","test","test","test",
                SecondJob.class,false,10,5);
    }
}

程序启动后,打印日志如下

2019-03-03 18:08:27.404  INFO 2652 --- [           main] com.he.SpringbootlessonApplication       : Started SpringbootlessonApplication in 7.25 seconds (JVM running for 7.759)
2019-03-03 18:08:27.407  INFO 2652 --- [           main] com.he.system.MyApplicationRunner        : --MyApplicationRunner--
2019-03-03 18:08:32.374  INFO 2652 --- [uartzScheduler]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 5 seconds
2019-03-03 18:08:32.374  INFO 2652 --- [uartzScheduler]] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2019-03-03 18:08:32.420  INFO 2652 --- [eduler_Worker-1] com.he.schedule.job.SecondJob            : second---: 18:08:32.420

以上这种动态方式,具有较大灵活性。适合一些根据代码逻辑,动态对定时任务进行修改的场景。

 

 

至此,Springboot2.0整合quartz完毕,以上内容仅为作者学习过程整理的笔记,有误的地方欢迎指正。

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值