Quartz与Spring Boot集成使用

上次自己搭建Quartz已经是几年前的事了,这次项目中需要定时任务,需要支持集群部署,想到比较轻量级的定时任务框架就是Quartz,于是来一波。

版本说明

通过搜索引擎很容易找到其官网,来到Document的页面,当前版本是2.2.x。

简单的搭建操作

通过Maven引入所需的包:

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.3</version>
</dependency>

通过quartz.properties设置相关配置:

# 线程调度器实例名
org.quartz.scheduler.instanceName = quartzScheduler
# 线程池的线程数,即最多3个任务同时跑
org.quartz.threadPool.threadCount = 3
# 使用内存存储任务和触发器等信息
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

定义任务,如MySimpleJob,再初始化Scheduler,最后将任务和出发器注册到Scheduler上:

package com.nicchagil.exercise.quartz.springbootquartzexercise;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.quartz.SimpleScheduleBuilder.simpleSchedule;

public class QuartzBoot {

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

    public static void main(String[] args) {

        try {
            // 获取调度器
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // 开启调度器
            scheduler.start();

            // 注册一个示例任务和触发器
            registerJobAndTrigger(scheduler);

            // scheduler.shutdown();

        } catch (SchedulerException se) {
            logger.error("调度器初始化异常", se);
        }
    }

    /**
     * 注册一个任务和触发器
     */
    public static void registerJobAndTrigger(Scheduler scheduler) {
        JobDetail job = JobBuilder.newJob(MySimpleJob.class)
                .withIdentity("mySimpleJob", "simpleGroup")
                .build();

        Trigger trigger = org.quartz.TriggerBuilder.newTrigger()
                .withIdentity("simpleTrigger", "simpleGroup")
                .startNow()
                .withSchedule(simpleSchedule()
                        .withIntervalInSeconds(10)
                        .repeatForever())
                .build();

        try {
            scheduler.scheduleJob(job, trigger);
        } catch (SchedulerException e) {
            logger.error("注册任务和触发器失败", e);
        }
    }

    /**
     * 简单的任务
     */
    public static class MySimpleJob implements Job {
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            logger.info("哇真的执行了");
        }
    }

}

启动日志,你可以看到任务按照指定的触发器跑:

13:31:28.759 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
13:31:28.828 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
13:31:28.828 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.2.3 created.
13:31:28.831 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
13:31:28.833 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

13:31:28.833 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'quartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
13:31:28.833 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.2.3
13:31:28.834 [main] INFO org.quartz.core.QuartzScheduler - Scheduler quartzScheduler_$_NON_CLUSTERED started.
13:31:28.834 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
13:31:28.853 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
13:31:28.860 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'simpleGroup.mySimpleJob', class=com.nicchagil.exercise.quartz.springbootquartzexercise.QuartzBoot$MySimpleJob
13:31:28.869 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
13:31:28.871 [quartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job simpleGroup.mySimpleJob
13:31:28.871 [quartzScheduler_Worker-1] INFO com.nicchagil.exercise.quartz.springbootquartzexercise.QuartzBoot - 哇真的执行了
13:31:38.842 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'simpleGroup.mySimpleJob', class=com.nicchagil.exercise.quartz.springbootquartzexercise.QuartzBoot$MySimpleJob
13:31:38.842 [quartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job simpleGroup.mySimpleJob
13:31:38.842 [quartzScheduler_Worker-2] INFO com.nicchagil.exercise.quartz.springbootquartzexercise.QuartzBoot - 哇真的执行了

扩展

查看StdSchedulerFactory.getDefaultScheduler(),会发现用的是new StdSchedulerFactory(),在initialize()方法可以看到默认从指定配置的文件或quartz.properties读取配置:

String requestedFile = System.getProperty("org.quartz.properties");
String propFileName = requestedFile != null ? requestedFile : "quartz.properties";

调度器待机状态、关闭状态

在调用scheduler.start()启动调度器后,可以使用scheduler.standby();将调度器转为待机状态,此状态下任务和触发器不会被触发。

另外,可以使用scheduler.shutdown()关闭调度器,是不可逆的,即调用后是不可以重新开始的。
它的参数不同,意义有所不同:

  • scheduler.shutdown()=scheduler.shutdown(false),方法会马上返回,正在执行的任务会继续执行
  • scheduler.shutdown(true),知道正在执行的任务执行完成才返回

getScheduler()可以看到使用SchedulerRepository缓存了Scheduler,使用的是HashMap<String, Scheduler>

SchedulerRepository schedRep = SchedulerRepository.getInstance();
Scheduler sched = schedRep.lookup(this.getSchedulerName());

与Spring Boot集成

首先,quartz.properties与之前的基本一致:

# 线程调度器实例名
org.quartz.scheduler.instanceName = quartzScheduler
# 线程池的线程数,即最多3个任务同时跑
org.quartz.threadPool.threadCount = 3
# 使用内存存储任务和触发器等信息
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

因会与Spring Scheduling集成,我们按照要求定义任务,需要特别注意的是@Component@EnableScheduling

package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz.job;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling
public class MyFirstExerciseJob {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public void myJobBusinessMethod() {
        this.logger.info("哇被触发了哈哈哈哈哈");
    }

}

类似的方式,可以定义MySecondExerciseJob

注册任务和触发器:

package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz;

import com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz.job.MyFirstExerciseJob;
import com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz.job.MySecondExerciseJob;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;

@Configuration
public class QuartzJobConfig {

    /**
     * 方法调用任务明细工厂Bean
     */
    @Bean(name = "myFirstExerciseJobBean")
    public MethodInvokingJobDetailFactoryBean myFirstExerciseJobBean(MyFirstExerciseJob myFirstExerciseJob) {
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        jobDetail.setConcurrent(false); // 是否并发
        jobDetail.setName("general-myFirstExerciseJob"); // 任务的名字
        jobDetail.setGroup("general"); // 任务的分组
        jobDetail.setTargetObject(myFirstExerciseJob); // 被执行的对象
        jobDetail.setTargetMethod("myJobBusinessMethod"); // 被执行的方法
        return jobDetail;
    }

    /**
     * 表达式触发器工厂Bean
     */
    @Bean(name = "myFirstExerciseJobTrigger")
    public CronTriggerFactoryBean myFirstExerciseJobTrigger(@Qualifier("myFirstExerciseJobBean") MethodInvokingJobDetailFactoryBean myFirstExerciseJobBean) {
        CronTriggerFactoryBean tigger = new CronTriggerFactoryBean();
        tigger.setJobDetail(myFirstExerciseJobBean.getObject());
        tigger.setCronExpression("0/10 * * * * ?"); // 什么是否触发,Spring Scheduler Cron表达式
        tigger.setName("general-myFirstExerciseJobTrigger");
        return tigger;
    }

    /**
     * 方法调用任务明细工厂Bean
     */
    @Bean(name = "mySecondExerciseJobBean")
    public MethodInvokingJobDetailFactoryBean mySecondExerciseJobBean(MySecondExerciseJob mySecondExerciseJob) {
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        jobDetail.setConcurrent(false); // 是否并发
        jobDetail.setName("general-mySecondExerciseJob"); // 任务的名字
        jobDetail.setGroup("general"); // 任务的分组
        jobDetail.setTargetObject(mySecondExerciseJob); // 被执行的对象
        jobDetail.setTargetMethod("myJobBusinessMethod"); // 被执行的方法
        return jobDetail;
    }

    /**
     * 表达式触发器工厂Bean
     */
    @Bean(name = "mySecondExerciseJobTrigger")
    public CronTriggerFactoryBean mySecondExerciseJobTrigger(@Qualifier("mySecondExerciseJobBean") MethodInvokingJobDetailFactoryBean mySecondExerciseJobDetailFactoryBean) {
        CronTriggerFactoryBean tigger = new CronTriggerFactoryBean();
        tigger.setJobDetail(mySecondExerciseJobDetailFactoryBean.getObject());
        tigger.setCronExpression("0/10 * * * * ?"); // 什么是否触发,Spring Scheduler Cron表达式
        tigger.setName("general-mySecondExerciseJobTrigger");
        return tigger;
    }

}

将任务和触发器注册到调度器:

package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz;

import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
public class QuartzConfig {

    /**
     * 调度器工厂Bean
     */
    @Bean(name = "schedulerFactory")
    public SchedulerFactoryBean schedulerFactory(@Qualifier("myFirstExerciseJobTrigger") Trigger myFirstExerciseJobTrigger,
                                                 @Qualifier("mySecondExerciseJobTrigger") Trigger mySecondExerciseJobTrigger) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        // 覆盖已存在的任务
        bean.setOverwriteExistingJobs(true);
        // 延时启动定时任务,避免系统未完全启动却开始执行定时任务的情况
        bean.setStartupDelay(15);
        // 注册触发器
        bean.setTriggers(myFirstExerciseJobTrigger, mySecondExerciseJobTrigger);
        return bean;
    }

}

持久化

任务持久化需要用到数据库,而初始化数据库的SQL可以从下载的发布版的文件中找到,比如,我在官网的Download页下载了当前版本的Full Distribution:Quartz 2.2.3 .tar.gz,解压后在quartz-2.2.3\docs\dbTables能找到初始化脚本,因我用的是MySQL的Innodb引擎,所以我用此脚本tables_mysql_innodb.sql

不能持久化的问题

如果使用的是MethodInvokingJobDetailFactoryBean,持久化会有如下报错:Couldn‘t store job: Unable to serialize JobDataMap for insertion into database because the value of,我们切换使用JobDetailFactoryBean

简单的持久化示例

quartz.properties的配置有所不同,比如会使用JobStoreTX,然后会指定数据源的信息:

# 线程调度器实例名
org.quartz.scheduler.instanceName = quartzScheduler
# 线程池的线程数,即最多3个任务同时跑
org.quartz.threadPool.threadCount = 3

# 如何存储任务和触发器等信息
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 驱动代理
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表前缀
org.quartz.jobStore.tablePrefix = qrtz_
# 数据源
org.quartz.jobStore.dataSource = quartzDataSource
# 是否集群
org.quartz.jobStore.isClustered = false

# 数据源
# 驱动
org.quartz.dataSource.quartzDataSource.driver = com.mysql.cj.jdbc.Driver
# 连接URL
org.quartz.dataSource.quartzDataSource.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8&useSSL=true&&serverTimezone=Asia/Shanghai
# 用户名
org.quartz.dataSource.quartzDataSource.user = root
# 密码
org.quartz.dataSource.quartzDataSource.password = 123456
# 最大连接数
org.quartz.dataSource.quartzDataSource.maxConnections = 5

定义的任务有所不同:

package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling
public class MyFirstExerciseJob implements Job {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        this.myJobBusinessMethod();
    }

    public void myJobBusinessMethod() {
        this.logger.info("哇被触发了哈哈哈哈哈");
    }

}

注册任务和触发器的方式有所不同:

   /**
     * 方法调用任务明细工厂Bean
     */
    @Bean(name = "myFirstExerciseJobBean")
    public JobDetailFactoryBean myFirstExerciseJobBean() {
        JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
        jobDetail.setName("general-myFirstExerciseJob"); // 任务的名字
        jobDetail.setGroup("general"); // 任务的分组
        jobDetail.setJobClass(MyFirstExerciseJob.class);
        jobDetail.setDurability(true);
        return jobDetail;
    }

    /**
     * 表达式触发器工厂Bean
     */
    @Bean(name = "myFirstExerciseJobTrigger")
    public CronTriggerFactoryBean myFirstExerciseJobTrigger(@Qualifier("myFirstExerciseJobBean") JobDetailFactoryBean myFirstExerciseJobBean) {
        CronTriggerFactoryBean tigger = new CronTriggerFactoryBean();
        tigger.setJobDetail(myFirstExerciseJobBean.getObject());
        tigger.setCronExpression("0/10 * * * * ?"); // 什么是否触发,Spring Scheduler Cron表达式
        tigger.setName("general-myFirstExerciseJobTrigger");
        return tigger;
    }

注册所有任务和触发器:

package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz;

import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

@Configuration
public class QuartzConfig {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    @Qualifier(value = "primaryDataSource")
    private DataSource primaryDataSource;

    /**
     * 调度器工厂Bean
     */
    @Bean(name = "schedulerFactory")
    public SchedulerFactoryBean schedulerFactory( Trigger... triggers) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();

        Properties p = new Properties();
        try {
            p.load(this.getClass().getClassLoader().getResourceAsStream("quartz.properties"));
        } catch (IOException e) {
           this.logger.error("加载quartz.properties失败", e);
           throw new Error(e);
        }
        bean.setQuartzProperties(p);

        // 覆盖已存在的任务
        bean.setOverwriteExistingJobs(true);
        // 延时启动定时任务,避免系统未完全启动却开始执行定时任务的情况
        bean.setStartupDelay(15);
        // 注册触发器
        bean.setTriggers(triggers);
        return bean;
    }

}

使用应用的数据源

有时候持久化的数据源用的是应用的数据源,可以使用bean.setDataSource(dataSource)设置或覆盖数据源。

比如,应用的数据源是这样的。
POM.XML:

        <!-- 引入数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>

        <!-- Spring Boot JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

数据源配置:

spring.dataSource.primaryDataSource.type = com.alibaba.druid.pool.DruidDataSource
spring.dataSource.primaryDataSource.url = jdbc:mysql://localhost:33306/quartz?characterEncoding=utf-8&useSSL=true&&serverTimezone=Asia/Shanghai&allowMultiQueries=true&autoReconnect=true
spring.dataSource.primaryDataSource.username = root
spring.dataSource.primaryDataSource.password = 123456
spring.dataSource.primaryDataSource.driverClassName = com.mysql.jdbc.Driver
spring.dataSource.primaryDataSource.initialSize = 5
spring.dataSource.primaryDataSource.minIdle = 5
spring.dataSource.primaryDataSource.maxActive = 15
spring.dataSource.primaryDataSource.maxWait = 60000
spring.dataSource.primaryDataSource.timeBetweenEvictionRunsMillis = 60000
spring.dataSource.primaryDataSource.minEvictableIdleTimeMillis = 300000
spring.dataSource.primaryDataSource.validationQuery = SELECT 1 FROM DUAL
spring.dataSource.primaryDataSource.testWhileIdle = true
spring.dataSource.primaryDataSource.testOnBorrow = true
spring.dataSource.primaryDataSource.testOnReturn = true

配置载入类:

package com.nicchagil.exercise.quartz.springbootquartzexercise.component.datasourcepool;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "spring.dataSource.primaryDataSource")
public class DruidPrimaryDataSourceConfigProperties {

    private String type;

    private String url;

    private String username;

    private String password;

    private String driverClassName;

    private Integer initialSize;

    private Integer minIdle;

    private Integer maxActive;

    private Integer maxWait;

    private Integer timeBetweenEvictionRunsMillis;

    private Integer minEvictableIdleTimeMillis;

    private String validationQuery;

    private Boolean testWhileIdle;

    private Boolean testOnBorrow;

    private Boolean testOnReturn;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public Integer getInitialSize() {
        return initialSize;
    }

    public void setInitialSize(Integer initialSize) {
        this.initialSize = initialSize;
    }

    public Integer getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(Integer minIdle) {
        this.minIdle = minIdle;
    }

    public Integer getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(Integer maxActive) {
        this.maxActive = maxActive;
    }

    public Integer getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(Integer maxWait) {
        this.maxWait = maxWait;
    }

    public Integer getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public Integer getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }

    public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public Boolean getTestWhileIdle() {
        return testWhileIdle;
    }

    public void setTestWhileIdle(Boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public Boolean getTestOnBorrow() {
        return testOnBorrow;
    }

    public void setTestOnBorrow(Boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public Boolean getTestOnReturn() {
        return testOnReturn;
    }

    public void setTestOnReturn(Boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }
}

初始化数据源:

package com.nicchagil.exercise.quartz.springbootquartzexercise.component.datasourcepool;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DruidPrimaryDataSourceConfig {

    @Autowired
    private DruidPrimaryDataSourceConfigProperties druidPrimaryDataSourceConfigProperties;

    @Bean
    public DataSource primaryDataSource (){
        DruidDataSource datasource = new DruidDataSource();
        /* 基础配置 */
        datasource.setUrl(this.druidPrimaryDataSourceConfigProperties.getUrl());
        datasource.setUsername(this.druidPrimaryDataSourceConfigProperties.getUsername());
        datasource.setPassword(this.druidPrimaryDataSourceConfigProperties.getPassword());
        datasource.setDriverClassName(this.druidPrimaryDataSourceConfigProperties.getDriverClassName());

        /* 其他配置 */
        datasource.setInitialSize(this.druidPrimaryDataSourceConfigProperties.getInitialSize());
        datasource.setMinIdle(this.druidPrimaryDataSourceConfigProperties.getMinIdle());
        datasource.setMaxActive(this.druidPrimaryDataSourceConfigProperties.getMaxActive());
        datasource.setMaxWait(this.druidPrimaryDataSourceConfigProperties.getMaxWait());
        datasource.setTimeBetweenEvictionRunsMillis(this.druidPrimaryDataSourceConfigProperties.getTimeBetweenEvictionRunsMillis());
        datasource.setMinEvictableIdleTimeMillis(this.druidPrimaryDataSourceConfigProperties.getMinEvictableIdleTimeMillis());
        datasource.setValidationQuery(this.druidPrimaryDataSourceConfigProperties.getValidationQuery());
        datasource.setTestWhileIdle(this.druidPrimaryDataSourceConfigProperties.getTestWhileIdle());
        datasource.setTestOnBorrow(this.druidPrimaryDataSourceConfigProperties.getTestOnBorrow());
        datasource.setTestOnReturn(this.druidPrimaryDataSourceConfigProperties.getTestOnReturn());

        return datasource;
    }

}

可以使用bean.setDataSource(dataSource)设置或覆盖数据源:

package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz;

import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

@Configuration
public class QuartzConfig {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    @Qualifier(value = "primaryDataSource")
    private DataSource primaryDataSource;

    /**
     * 调度器工厂Bean
     */
    @Bean(name = "schedulerFactory")
    public SchedulerFactoryBean schedulerFactory( Trigger... triggers) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();

        Properties p = new Properties();
        try {
            p.load(this.getClass().getClassLoader().getResourceAsStream("quartz.properties"));
        } catch (IOException e) {
           this.logger.error("加载quartz.properties失败", e);
           throw new Error(e);
        }
        bean.setQuartzProperties(p);

        /* 使用应用的数据源 */
        bean.setDataSource(this.primaryDataSource);

        // 覆盖已存在的任务
        bean.setOverwriteExistingJobs(true);
        // 延时启动定时任务,避免系统未完全启动却开始执行定时任务的情况
        bean.setStartupDelay(15);
        // 注册触发器
        bean.setTriggers(triggers);
        return bean;
    }

}

数据表查询

QRTZ_CRON_TRIGGERS,触发器表

QRTZ_FIRED_TRIGGERS,已触发的触发表

QRTZ_JOB_DETAILS,任务明细表

QRTZ_TRIGGERS,触发器表

QRTZ_LOCKS,锁表

并发执行

保证上一次任务执行完毕,再执行下一次任务

在任务类上注解@DisallowConcurrentExecution,比如此任务需耗时7秒,却配置5秒执行一次,注解后将会7秒才运行一次:

@Component
@EnableScheduling
@DisallowConcurrentExecution // 保证上一次任务执行完毕再执行下一任务
public class MyFirstExerciseJob implements Job {

日志:

2018-02-21 12:09:04.479  INFO 9496 --- [actory_Worker-3] c.n.e.q.s.c.q.job.MyFirstExerciseJob     : 哇被触发了哈哈哈哈哈 x 5
2018-02-21 12:09:11.629  INFO 9496 --- [actory_Worker-2] c.n.e.q.s.c.q.job.MyFirstExerciseJob     : 哇被触发了哈哈哈哈哈 x 6
2018-02-21 12:09:18.796  INFO 9496 --- [actory_Worker-1] c.n.e.q.s.c.q.job.MyFirstExerciseJob     : 哇被触发了哈哈哈哈哈 x 7
2018-02-21 12:09:26.016  INFO 9496 --- [actory_Worker-3] c.n.e.q.s.c.q.job.MyFirstExerciseJob     : 哇被触发了哈哈哈哈哈 x 8
2018-02-21 12:09:33.268  INFO 9496 --- [actory_Worker-2] c.n.e.q.s.c.q.job.MyFirstExerciseJob     : 哇被触发了哈哈哈哈哈 x 9
2018-02-21 12:09:40.518  INFO 9496 --- [actory_Worker-1] c.n.e.q.s.c.q.job.MyFirstExerciseJob     : 哇被触发了哈哈哈哈哈 x 10
2018-02-21 12:09:47.668  INFO 9496 --- [actory_Worker-3] c.n.e.q.s.c.q.job.MyFirstExerciseJob     : 哇被触发了哈哈哈哈哈 x 11
2018-02-21 12:09:54.869  INFO 9496 --- [actory_Worker-2] c.n.e.q.s.c.q.job.MyFirstExerciseJob     : 哇被触发了哈哈哈哈哈 x 12

集群

下面配置展示了Quartz的必要配置:

  • instanceName,实例名,集群各节点的实例名相同
  • instanceId,实例ID,设为AUTO则由Quartz自动根据主机名、时间戳生成实例ID
  • org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX,存储任务为数据库存储,不能使用内存,即RAMJobStore
  • isClustered,告诉Quartz是否为集群模式
# 线程调度器实例名
org.quartz.scheduler.instanceName = QuartzScheduler
# 线程池的线程数,即最多3个任务同时跑
org.quartz.threadPool.threadCount = 3
# 实例ID
org.quartz.scheduler.instanceId = AUTO

# 如何存储任务和触发器等信息
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 驱动代理
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表前缀
org.quartz.jobStore.tablePrefix = qrtz_
# 是否集群
org.quartz.jobStore.isClustered = true

配置后,启动多个节点,你会发现:

  • 一个任务只有一个节点触发,不会多节点都被触发
  • 当一个节点宕机,其它节点会接手任务的触发

原文地址:https://www.cnblogs.com/nick-huang/p/8456272.html

时间: 2024-08-09 01:38:07

Quartz与Spring Boot集成使用的相关文章

Spring Boot集成Spring Scheduler和Quartz Scheduler

本文介绍了Spring Boot集成Spring Scheduler和Quartz Scheduler的基础知识,利用ShedLock解决Spring Scheduler多实例运行冲突,介绍了Quartz ScheduleBuilder.Calendar,介绍了动态创建Quartz Job的方法. GitHub源码 Spring Scheduler Spring Framework提供了简单.易用的Job调度框架Spring Scheduler. 示例 在Spring Boot中,只需两步即可启

170711、spring boot 集成shiro

这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在Java领域一般有Spring Security.Apache Shiro等安全框架,但是由于Spring Security过于庞大和复杂,大多数公司会选择Apache Shiro来使用,这篇文章会先介绍一下Apache Shiro,在结合Spring Boot给出使用案例. Apache Shiro What is Apache Shiro?

Spring Boot集成Jasypt安全框架

Jasypt安全框架提供了Spring的集成,主要是实现 PlaceholderConfigurerSupport类或者其子类. 在Sring 3.1之后,则推荐使用PropertySourcesPlaceholderConfigurer类作为属性替换配置类,这里Spring集成Jasypt则使用Jasypt对属性替换配置类的实现.EncryptablePropertySourcesPlaceholderConfigurer. 在Spring中集成比较容易,而且Jasypt官方也给出了配置Bea

spring boot 集成 hbase

spring boot 集成 hbase 会启动报错 主要因为Spring Boot内嵌了Web容器,方便对应用进行微服务化开发和部署.所以打算将HBase的业务应用作为一个单服务进行开发和发布,其他相关的子系统通过RESTful API来访问. 搭建项目环境时,需要注意的事项: 由于Spring Boot内嵌了Web容器,所以框架默认导入了依赖:tomcat-embed-core-8.5.5.jar.tomcat-embed-el-8.5.5.jar等包.而HBase的jar中包含了:serv

spring boot集成swagger2

做java Web的后端开发已经两年多了,一般都是开发完了接口,都把接口更新到wiki文档上,然后通知前端去文档上去查阅接口的详细描述, 当时经常接口会有变动,加参数或返回值夹字段,所以维护语线上一致的文档是一件非常麻烦的事情,前一段时间同事聊天说他们公司用的swagger2,这个不需要写文档,它是自动生成文档,只要会使用它提供的几个的注解就行,于是上网找了下资料,发现它于spring boot集成也非常方便.不废话直接看了代码. 首先,在maven项目的pom.xml加上他需要的依赖. <de

Spring Boot 集成MyBatis

Spring Boot 集成MyBatis 在集成MyBatis前,我们先配置一个druid数据源. Spring Boot 系列 Spring Boot 入门 Spring Boot 属性配置和使用 Spring Boot 集成MyBatis Spring Boot 静态资源处理 Spring Boot - 配置排序依赖技巧 Spring Boot - DevTools 介绍 Spring Boot 集成druid druid有非常多个配置选项,使用Spring Boot 的配置文件能够方便的

Quartz与Spring的集成

一.Quartz简介 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序.Jobs可以做成标准的Java组件或 EJBs.Quartz 是个开源的作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制.Quartz 允许开发人员根据时间间隔(或天)来调度作业.它实现了作业和触发器的多对多关系,

Kafka 入门和 Spring Boot 集成

Kafka 入门和 Spring Boot 集成 标签:博客 [TOC] 概述 kafka 是一个高性能的消息队列,也是一个分布式流处理平台(这里的流指的是数据流).由java 和 Scala 语言编写,最早由 LinkedIn 开发,并 2011年开源,现在由 Apache 开发维护. 应用场景 下面列举了一些kafka常见的应用场景. 消息队列 : Kafka 可以作为消息队列使用,可用于系统内异步解耦,流量削峰等场景. 应用监控:利用 Kafka 采集应用程序和服务器健康相关的指标,如应用

Spring Boot集成MyBatis实现通用Mapper

前言 MyBatis关于MyBatis,大部分人都很熟悉.MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.不管是DDD(Domain Driven Design,领域驱动建模)还是分层架构的风