恒宇少年 · 2019年12月24日

这种方式整合Quartz你见过吗?

知识改变命运,撸码使我快乐,2019年你的发迹线还好吗?<br/>
点赞再看,养成习惯<br/>
本篇文章对应源码码云(Gitee)仓库<br/>
https://gitee.com/minbox-projects/api-boot-chapter,您的Star是给我最大动力

Quartz是一款优秀的任务调度框架,支持内存、JDBC的形式来存储未执行的任务列表,支持多个任务节点同时执行任务,支持任务漂移到不同的节点执行。

前言

这么优秀的任务调度框架我想应该是很多开发者的首选解决方案,因此ApiBoot对它下手了,基于SpringBoot封装了名为ApiBoot Quartz的组件,同样是通过application.yml配置文件的形式就可以简单的实现初始化集成。

如果使用默认的配置,我们可以不编写一行集成相关的代码,ApiBoot Quartz还针对日常高频率使用的方法提供了一个接口,定义的方法如:创建任务、暂停任务、恢复任务、删除任务等等。

任务存储方式

Quartz自身提供了两种存储任务的方式:

  • Memory:内存方式,将任务存储到内存中,当项目重启时就会丢失,不建议生产环境使用。
  • Jdbc:数据库方式,将任务存储到Quartz提供的固定结构的表内,项目重启任务不会丢失,多种数据库的建表语句请访问:Quartz Schemas 按需选择。

ApiBootQuartz内提供的两种存储方式进行了封装,通过api.boot.quartz.job-store-type参数进行配置,该参数默认值为memory,所以你如果使用内存方式该参数不需要修改,更多配置请访问 ApiBootQuartzProperties 查看。

任务类型

任务类型是ApiBoot Quartz的新概念,其实在Quartz中任务并没有类型区分,实现org.quartz.Job接口就可以创建一个任务。

不过Spring也是爱折腾,对其进行了封装提供了QuartzJobBean,它是一个抽象类,我们继承该类后也可以创建一个定时任务。

ApiBoot Quartz中有三种任务类型,分别是:

  • OnceJob:仅执行一次的任务类型
  • CronJob:使用Cron表达式来定义任务的执行周期
  • LoopJob:可指定循环次数的任务,根据指定循环的次数进行重复执行

内置方法

ApiBoot封装Quartz后所提供的方法都位于 ApiBootQuartzService 接口中,而该接口有一个默认的实现类 ApiBootQuartzServiceDefaultSupport ,该实现类全部实现了接口定义方法,内部通过org.quartz.Scheduler来实现任务的基本操作。

内置方法列表:

方法描述
Scheduler getScheduler();获取SpringIoc容器内的Scheduler实例
String newJob(ApiBootJobWrapper jobWrapper);通过封装的对象创建一个新的任务,这是创建所有类型任务的入口
void deleteJob(String jobKey);删除一个任务
void deleteJobs(String... jobKeys);删除一系列任务
void deleteJobs(Collection<String> jobKeys);删除集合内的所有任务
void pauseJob(String jobKey);暂停一个任务
void pauseJobs(String... jobKeys);暂停传递的所有任务
void pauseJobs(Collection<String> jobKeys);暂停集合内的所有任务
void resumeJob(String jobKey);恢复一个任务执行
void resumeJobs(String... jobKeys);恢复数组内的所有任务执行
void resumeJobs(Collection<String> jobKeys);恢复集合内的所有任务执行
void updateJobCron(String jobKey, String cron);更新任务Cron表达式
void updateJobStartTime(String jobKey, Date jobStartTime);更新任务开始时间
void startAllJobs();启动所有定时任务
void shutdownAllJobs();关闭所有定时任务

既然ApiBoot为我们提供了这么多内置的方法,我们接下来创建一个项目来感受一下。

示例项目

使用Idea创建一个SpringBoot项目,我们把本章所需要的依赖添加在pom.xml文件内,如下所示:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!--ApiBoot Quartz-->
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-quartz</artifactId>
  </dependency>
</dependencies>
<dependencyManagement>
  <dependencies>
    <!--ApiBoot统一版本-->
    <dependency>
      <groupId>org.minbox.framework</groupId>
      <artifactId>api-boot-dependencies</artifactId>
      <version>2.2.1.RELEASE</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>
注意:ApiBoot的版本是根据SpringBoot的版本而定义的,详见 ApiBoot 版本分支)

示例任务类

我们来创建一个名为DemoJob的任务调度示例类,如下所示:

/**
 * 示例任务
 *
 * @author 恒宇少年
 */
public class DemoJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("任务执行了..");
    }
}

QuartzJobBean是什么?

QuartzJobBean是由Spring提供,实现org.quartz.Job接口,是对Quartz内置任务接口的实现并且封装。

QuartzJobBean的优势

Spring所提供的QuartzJobBean具体有什么优势呢?

  • 自动将实现类实例加入IOC

    使用QuartzJobBean来创建自定义任务时,Spring会自动扫描项目内的实现类,将每一个实现类通过反射机制创建出实例并将实例写入到IOC容器内。

  • 可在实现类内注入实例

    直接使用Quartz时,如果自定义任务类实例不加入IOC容器,我们无法在自定义任务类注入Service,这一点了解Spring基础的同学应该都明白,我们无法在非被IOC托管的类内进行注入操作,而使用QuartzJobBean则不用考虑这一点。

QuartzJobBean注册流程

使用ApiBoot Quartz后为什么不需要再手动添加注入把任务实现类加入到IOC容器?

任务类注册流程如下所示:

  • 第一步:ApiBootQuartzAutoConfiguration#quartzScheduler()

    ApiBoot在集成Quartz时提供了一个自动化配置类 ApiBootQuartzAutoConfiguration ,在该类中通过quartzScheduler()方法来自动创建一个SchedulerFactoryBean实例

  • 第二步:SchedulerFactoryBean#setJobFactory()

    通过SchedulerFactoryBean内提供的setJobFactory()方法可以自定义设置具体使用JobFactory的实现类,而在spring-context-support依赖内已经提供了相关实现,名为 SpringBeanJobFactory

  • 第三步:SpringBeanJobFactory#createJobInstance()

    在项目启动时会将扫描到的所有QuartzJobBean实现类通过JobFactory#newJob方法进行创建任务实例后将实例交付给Quartz框架进行准备后期的任务调度。

SpringBeanJobFactory类继承于AdaptableJobFactory类,而AdaptableJobFactory则是实现了JobFactory接口,这样的话如果我们将SpringBeanJobFactory设置给SchedulerFactoryBean项目启动时Quartz就会调用SpringBeanJobFactory#createJobInstance()方法来创建任务实例。

而在createJobInstance()方法内Spring则是将创建的任务实例存入了IOC容器内,这样一来我们的自定义任务内就可以进行注入其他Bean的操作了,该方法源码如下所示:

/**
    * Create the job instance, populating it with property values taken
    * from the scheduler context, job data map and trigger data map.
    */
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
  Object job = (this.applicationContext != null ?
                // 通过ApplicationContext创建任务实例,并添加到IOC
                this.applicationContext.getAutowireCapableBeanFactory().createBean(
                  bundle.getJobDetail().getJobClass(), AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false) :
                super.createJobInstance(bundle));

  if (isEligibleForPropertyPopulation(job)) {
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
    MutablePropertyValues pvs = new MutablePropertyValues();
    if (this.schedulerContext != null) {
      pvs.addPropertyValues(this.schedulerContext);
    }
    pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
    pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
    if (this.ignoredUnknownProperties != null) {
      for (String propName : this.ignoredUnknownProperties) {
        if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
          pvs.removePropertyValue(propName);
        }
      }
      bw.setPropertyValues(pvs);
    }
    else {
      bw.setPropertyValues(pvs, true);
    }
  }

  return job;
}

大致的QuartzJobBean实现类注册流程就是这个样子的,下面让我们来见识下是不是真的有那么简单就可以创建并执行一个任务。

运行测试

为了演示方便,我们修改XxxApplication入口类,让项目启动后自动执行DemoJob任务,如下所示:

@SpringBootApplication
public class ApibootQuartzIntegratedAwayApplication implements CommandLineRunner {

  public static void main(String[] args) {
    SpringApplication.run(ApibootQuartzIntegratedAwayApplication.class, args);
  }

  /**
    * ApiBoot Quartz内置方法接口
    * {@link org.minbox.framework.api.boot.plugin.quartz.support.ApiBootQuartzServiceDefaultSupport}
    */
  @Autowired
  private ApiBootQuartzService quartzService;

  @Override
  public void run(String... args) throws Exception {
    quartzService.newJob(ApiBootOnceJobWrapper.Context()
                         .jobClass(DemoJob.class)
                         .wrapper());
  }
}
CommandLineRunner是由SpringBoot提供用于执行项目启动后的逻辑。

启动项目后控制台输出内容如下所示:

......
任务执行了..

心细的同学应该看到了我们使用了ApiBootOnceJobWeapper这个封装类来创建的任务对象,我们下一章就来讲下这个封装类到底可以干什么?

敲黑板,划重点

Quartz是一个优秀的分布式任务调度框架,ApiBoot封装了它,使它插上了翅膀,让我们明白了简单的另一层定义。

代码示例

如果您喜欢本篇文章请为源码仓库点个Star,谢谢!!!
本篇文章示例源码可以通过以下途径获取,目录为apiboot-quartz-integrated-away

作者个人 博客
使用开源框架 ApiBoot 助你成为Api接口服务架构师
推荐阅读
关注数
0
文章数
49
博客地址:[链接]开源框架ApiBoot官网:[链接]
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息