Spring Boot学习(二十):Spring Boot整合Quartz ,一个强大的定时框架

前言

Spring Boot系列: 点击查看Spring Boot系列文章


Quartz 介绍

Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。

Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。

Quartz 允许程序开发人员根据时间的间隔来调度作业。

Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。


Quartz 核心概念

我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。

1、Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)

2、JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。

3、Trigger 代表一个调度参数的配置,什么时候去调。触发器Trigger最基本的功能是指定Job的执行时间,执行间隔,运行次数等。

4、Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。


Quartz API

Quartz API的关键接口是:

Scheduler - 与调度程序交互的主要API。

Job - 希望由调度程序执行的任务作业。

JobDetail - 用于定义作业的实例。

Trigger(即触发器) - 定义执行给定作业的计划组件。

JobBuilder - 用于定义/构建JobDetail实例,用于定义作业的实例。

TriggerBuilder - 用于定义/构建触发器实例。

Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger。

注:Job和Trigger的创建和运行等操作都是通过Scheduler执行的

Quartz 执行流程

当Job的一个trigger被触发时,execute()方法由调度程序的一个工作线程调用。execute()方法会被scheduler的一个工作线程调用;execute()方法的JobExecutionContext对象中保存着该job运行时的一些信息 ,执行job的scheduler的引用,触发job的trigger的引用,JobDetail对象引用,以及一些其它信息。

JobDetail对象是在将job加入scheduler时,由客户端程序(你的程序)创建的。它包含job的各种属性设置,以及用于存储job实例状态信息的JobDataMap。

Trigger用于触发Job的执行。当你准备调度一个job时,你创建一个Trigger的实例,然后设置调度相关的属性。Trigger也有一个相关联的JobDataMap,用于给Job传递一些触发相关的参数。Quartz自带了各种不同类型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。

SimpleTrigger主要用于一次性执行的Job(只在某个特定的时间点执行一次),或者Job在特定的时间点执行,重复执行N次,每次执行间隔T个时间单位。CronTrigger在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等。

为什么既有Job,又有Trigger呢?很多任务调度器并不区分Job和Trigger。有些调度器只是简单地通过一个执行时间和一些job标识符来定义一个Job;其它的一些调度器将Quartz的Job和Trigger对象合二为一。在开发Quartz的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。

例如,Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以修改或者替换Trigger,而不用重新定义与之关联的Job。


Spring Boot整合Quartz

1、导入依赖,导入的是springboot集成的quartz,这样我们使用起来可以方便很多。

        <!--spring boot集成quartz-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

2、我们可以在配置文件进行Quartz的相关配置,当然我们不配置也可以使用Quartz,只不过使用的是默认配置。大家根据自己的需求进行配置

spring:
  #配置数据源
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testquartz?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: password
  quartz:
    #持久化到数据库方式
    job-store-type: jdbc
     #初始化表结构
    initialize-schema: embedded
    properties:
      org:
        quartz:
          scheduler:
            instanceName: MyScheduler
            instanceId: AUTO
          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

3、在启动类添加@EnableScheduling注解,开启定时任务
@SpringBootApplication
@EnableScheduling
public class DemoApplication {

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

}

4、定义两个个job,一个cron类型的,一个simple的类型的
//定义job类很简单,只需要在springboot中继承QuartzJobBean 类,然后覆写executeInternal方法即可

public class QuartzTestJob extends QuartzJobBean {

    private static final Logger logger = LoggerFactory.getLogger(QuartzTestJob.class);


    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        logger.info("执行cron定时任务");
    }
}


public class SimpleTestJob extends QuartzJobBean {

    private static final Logger logger = LoggerFactory.getLogger(SimpleTestJob.class);

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        logger.info("执行simple定时任务");
    }
}

我们可以看一下QuartzJobBean的 源码:
其实QuartzJobBean就是一个实现Job接口 的类,它替我们实现了execute方法,然后替我们封装了一个executeInternal方法来表示执行方法

public abstract class QuartzJobBean implements Job {

	/**
	 * This implementation applies the passed-in job data map as bean property
	 * values, and delegates to {@code executeInternal} afterwards.
	 * @see #executeInternal
	 */
	@Override
	public final void execute(JobExecutionContext context) throws JobExecutionException {
		try {
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			MutablePropertyValues pvs = new MutablePropertyValues();
			pvs.addPropertyValues(context.getScheduler().getContext());
			pvs.addPropertyValues(context.getMergedJobDataMap());
			bw.setPropertyValues(pvs, true);
		}
		catch (SchedulerException ex) {
			throw new JobExecutionException(ex);
		}
		executeInternal(context);
	}

	/**
	 * Execute the actual job. The job data map will already have been
	 * applied as bean property values by execute. The contract is
	 * exactly the same as for the standard Quartz execute method.
	 * @see #execute
	 */
	protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;

}


5、创建好任务之后,我们就可以创建一个配置类,进行JobDetail 和 Trigger 触发器的配置

@Configuration
public class QuartzConfig {

    /**
     * 创建JobDetail
     * @return
     */
    @Bean
    JobDetailFactoryBean jobDetailFactoryBean() {
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        //设置我们的PrintTimeJob业务类
        bean.setJobClass(QuartzTestJob.class);
        //每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
        JobDataMap map = new JobDataMap();
        map.put("msg", "hello");
        bean.setJobDataMap(map);
        //指定作业的持久性,即是否应该保留它。为true,表示即使没有触发器指向这个job,也保留该job
        bean.setDurability(true);
        return bean;
    }

    @Bean
    JobDetailFactoryBean simJobDetailFactoryBean() {
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        //设置我们的PrintTimeJob业务类
        bean.setJobClass(SimpleTestJob.class);
        //每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
        JobDataMap map = new JobDataMap();
        map.put("msg", "hello");
        bean.setJobDataMap(map);
        //指定作业的持久性,即是否应该保留它。为true,表示即使没有触发器指向这个job,也保留该job
        bean.setDurability(true);
        return bean;
    }

    /**
     * 创建Trigger触发器
     * @return
     */
    @Bean
    CronTriggerFactoryBean cronTriggerFactoryBean(){
        CronTriggerFactoryBean cronTriggerFactoryBean=new CronTriggerFactoryBean();
//        设置cron表达式
        cronTriggerFactoryBean.setCronExpression("0/3 * * * * ?");
//        设置JobDetail
        cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean().getObject());
        JobDataMap jobDataMap=new JobDataMap();
        jobDataMap.put("msg","cron");
//        设置job的DataMap
        cronTriggerFactoryBean.setJobDataMap(jobDataMap);
//        设置Trigger名称
        cronTriggerFactoryBean.setName("cronTrigger");
        return cronTriggerFactoryBean;
    }

    @Bean
    public SimpleTriggerFactoryBean  simpleTriggerFactoryBean(){
        SimpleTriggerFactoryBean  simpleTriggerFactoryBean=new SimpleTriggerFactoryBean();
//        指定这个触发器的执行时间间隔。,单位为毫秒
        simpleTriggerFactoryBean.setRepeatInterval(5000);
//        执行的次数,执行了这么多次后就不再执行
        simpleTriggerFactoryBean.setRepeatCount(3);
//        设置JobDetail
        simpleTriggerFactoryBean.setJobDetail(simJobDetailFactoryBean().getObject());
//        设置触发器的具体开始时间。
//        simpleTriggerFactoryBean.setStartTime(new Date());

        return simpleTriggerFactoryBean;
    }

    /**
     * 创建Scheduler
     * @return
     */
    @Bean
    SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setTriggers(cronTriggerFactoryBean().getObject(), simpleTriggerFactoryBean().getObject());
        return bean;
    }


}

在spring boot中,我们配置JobDetail 是使用JobDetailFactoryBean ,注册一个JobDetailFactoryBean 的bean,就相当于是配置了一个JobDetail 。

配置Trigger 触发器,我们可以选择使用SimpleTriggerFactoryBean还是CronTriggerFactoryBean ,CronTriggerFactoryBean 类型触发器一般用于定时任务,因为它可以使用cron表达式,使用起来更加灵活。而一般类型的job工作,直接使用SimpleTriggerFactoryBean即可。配置相应的bean即可。

我们前面说过,Job和Trigger的操作都是通过Scheduler来执行,所以最后我们还要配置一个SchedulerFactoryBean的bean,相当于使用这个 Scheduler来调度job和trigger。

除了FactoryBean,我们还可以使用JobBuilder和TriggerBuilder来创建job和trigger,这也是官方的创建方法

@Configuration
public class BuilderQuartzConfig {

//    定义JobDetail,使用JobBuilder的build方法创建JobDetail
    @Bean
    public JobDetail builderJobDetail(){
        return JobBuilder
                .newJob(BuilderJob.class)//通过class引入job类
                .withIdentity("builderJob","builderGroup")//定义任务名称和分组
                .storeDurably()//当job没有触发器绑定时,保留该job,不调用该方法,则会抛弃job
                .build();
    }

    //定义触发器,使用TriggerBuilder的build方法
    @Bean
    public Trigger orderTrigger() {
		/*SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule()
				.withIntervalInMinutes(1)	//定义时间周期
				.repeatForever();*/
        //定义调度器
        //"0/20 * * * * ?"   表示每隔20秒执行一次,withMisfireHandlingInstructionDoNothing为错失不补偿
        CronScheduleBuilder scheduleBuilder
                = CronScheduleBuilder.cronSchedule("0/20 * * * * ?");
        return TriggerBuilder
                .newTrigger()
                .forJob(builderJobDetail())//关联我们定义的job
                .withIdentity("builderTrigger")//定义名称
                .withSchedule(scheduleBuilder)//关联Schedule调度程序
                .build();
    }

}

大家可以自行选择使用哪种方法来定义

启动项目,我们的调度任务就会开启执行了。

在这里插入图片描述

你可能感兴趣的:(springboot,spring,quartz,java,spring,boot,后端)