SpringBoot整合Quartz 实现分布式定时任务调度

2023-05-16

一、Quartz 集群架构
Quartz 是 Java 领域最著名的开源任务调度工具。

在上篇文章中,我们详细的介绍了 Quartz 的单体应用实践,如果只在单体环境中应用,Quartz 未必是最好的选择,例如Spring Scheduled一样也可以实现任务调度,并且与SpringBoot无缝集成,支持注解配置,非常简单,但是它有个缺点就是在集群环境下,会导致任务被重复调度!

而与之对应的 Quartz 提供了极为广用的特性,如任务持久化、集群部署和分布式调度任务等等,正因如此,基于 Quartz 任务调度功能在系统开发中应用极为广泛!

在集群环境下,Quartz 集群中的每个节点是一个独立的 Quartz 应用,没有负责集中管理的节点,而是通过数据库表来感知另一个应用,利用数据库锁的方式来实现集群环境下进行并发控制,每个任务当前运行的有效节点有且只有一个!

 

特别需要注意的是:分布式部署时需要保证各个节点的系统时间一致!

 二、数据表初始化
数据库表结构官网已经提供,我们可以直接访问Quartz对应的官方网站,找到对应的版本,然后将其下载!

 

 我选择的是quartz-2.3.0-distribution.tar.gz,下载完成之后将其解压,在文件中搜索sql,在里面选择适合当前环境的数据库脚本文件,然后将其初始化到数据库中即可!

 

例如,我使用的数据库是mysql-5.7,因此我选择的是tables_mysql_innodb.sql脚本,具体内容如下: 
 

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
 
CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(190) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(190) NOT NULL,
    TRIGGER_GROUP VARCHAR(190) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(190) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(190) NULL,
JOB_GROUP VARCHAR(190) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;
 
CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;
 
CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);
 
CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
 
CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
 
commit;

其中,QRTZ_LOCKS 就是 Quartz 集群实现同步机制的行锁表!

三、Quartz 集群实践

1、引入quartz依赖


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

 2、创建 application.properties 配置文件,再配置文件中加入quartz配置

#调度配置
org:
  quartz:
    scheduler:
      #调度器实例名称
      instanceName: MaterialFromEamScheduler
      #调度器实例编号自动生成
      instanceId: AUTO
      #是否在Quartz执行一个job前使用UserTransaction
      wrapJobExecutionInUserTransaction: false
#线程池配置
    threadPool:
      #线程池的实现类
      class: org.quartz.simpl.SimpleThreadPool
      #线程池中的线程数量
      threadCount: 10
      #线程优先级
      threadPriority: 5
      #配置是否启动自动加载数据库内的定时任务,默认true
      threadsInheritContextClassLoaderOfInitializingThread: true
    #持久化方式配置
    jobStore:
      #JobDataMaps是否都为String类型
      useProperties: true
      #数据表的前缀,默认QRTZ_
      tablePrefix: QRTZ_
      #最大能忍受的触发超时时间
      misfireThreshold: 60000
      #是否以集群方式运行
      isClustered: true
      #调度实例失效的检查时间间隔,单位毫秒
      clusterCheckinInterval: 2000
      maxMisfiresToHandleAtATime: 1
      #数据保存方式为数据库持久化
      class: org.quartz.impl.jdbcjobstore.JobStoreTX
      #数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
      driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
      # 是否开启悲观锁控制集群中trigger并发
      acquireTriggersWithinLock: true
      #数据库别名 随便取
      dataSource: qzDS
      #数据库连接池,将其设置为druid
    dataSource:
      qzDS:
        connectionProvider:
          class: com.mes.material.quartz.provider.DruidConnectionProvider
        #数据库引擎
        driver: org.postgresql.Driver
        #数据库连接
        URL: jdbc:postgresql://127.0.0.1:5432/mes_cloud_stag_yqs?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
        #数据库用户
        user: postgres
        #数据库密码
        password: postgres
        #允许最大连接
        maxConnection: 100

3、注册 Quartz 任务工厂

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class QuartzJobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        //调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        //进行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

4、注册调度工厂

本文采用自定义数据源实现




import com.mes.material.constants.MaterialConstants;
import com.mes.material.quartz.factory.QuartzJobFactory;
import com.mes.material.quartz.listener.SimpleJobListener;
import com.mes.material.quartz.listener.SimpleSchedulerListener;
import com.mes.material.quartz.listener.SimpleTriggerListener;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.KeyMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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;

/** @Author: 
 * @Description: 3.5、注册调度工厂
 * @Date: 15:42 2022/8/10
 * @Param
 * @return
 **/

@Configuration
public class QuartzConfig {

    @Autowired
    private QuartzJobFactory jobFactory;

    @Autowired
    private SimpleSchedulerListener simpleSchedulerListener;

    @Autowired
    private SimpleJobListener simpleJobListener;

    @Autowired
    private SimpleTriggerListener simpleTriggerListener;
    @Autowired
    private DataSource dataSource;

    @Value("${org.quartz.scheduler.instanceName}")
    private String instanceName;
    @Value("${org.quartz.scheduler.instanceId}")
    private String instanceId;
    @Value("${org.quartz.threadPool.class}")
    private String threadPoolClass;
    @Value("${org.quartz.threadPool.threadCount}")
    private String threadCount;
    @Value("${org.quartz.threadPool.threadPriority}")
    private String threadPriority;
    @Value("${org.quartz.jobStore.class}")
    private String jobStoreClass;
    @Value("${org.quartz.jobStore.isClustered}")
    private String isClustered;
    @Value("${org.quartz.jobStore.clusterCheckinInterval}")
    private String clusterCheckinInterval;
    @Value("${org.quartz.jobStore.maxMisfiresToHandleAtATime}")
    private String maxMisfiresToHandleAtATime;
    @Value("${org.quartz.jobStore.misfireThreshold}")
    private String misfireThreshold;
    @Value("${org.quartz.jobStore.tablePrefix}")
    private String tablePrefix;
    @Value("${org.quartz.jobStore.acquireTriggersWithinLock}")
    private String acquireTriggersWithinLock;
    @Value("${org.quartz.jobStore.driverDelegateClass}")
    private String driverDelegateClass;
    @Value("${org.quartz.jobStore.dataSource}")
    private String dataSourceName;
    @Value("${org.quartz.dataSource.qzDS.connectionProvider.class}")
    private String connectionProviderClass;
    @Value("${org.quartz.dataSource.qzDS.driver}")
    private String driver;
    @Value("${org.quartz.dataSource.qzDS.URL}")
    private String url;
    @Value("${org.quartz.dataSource.qzDS.user}")
    private String user;
    @Value("${org.quartz.dataSource.qzDS.password}")
    private String password;
    @Value("${org.quartz.dataSource.qzDS.maxConnection}")
    private String maxConnection;
    @Value("${org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread}")
    private String threadsInheritContextClassLoaderOfInitializingThread;


    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        //创建SchedulerFactoryBean
        SchedulerFactoryBean factory = new SchedulerFactoryBean();

        //quartz参数
        Properties prop = new Properties();
        //调度器实例名称
        prop.put("org.quartz.scheduler.instanceName", instanceName);
        //调度器实例编号自动生成
        prop.put("org.quartz.scheduler.instanceId", instanceId);
        //线程池配置--线程池的实现类
        prop.put("org.quartz.threadPool.class", threadPoolClass);
        //线程池中的线程数量
        prop.put("org.quartz.threadPool.threadCount", threadCount);
        //配置是否启动自动加载数据库内的定时任务,默认true
        prop.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", threadsInheritContextClassLoaderOfInitializingThread);
        //线程优先级
        prop.put("org.quartz.threadPool.threadPriority", threadPriority);
        //JobStore配置-数据保存方式为数据库持久化
        prop.put("org.quartz.jobStore.class", jobStoreClass);
        //集群配置--是否以集群方式运行
        prop.put("org.quartz.jobStore.isClustered", isClustered);
        //调度实例失效的检查时间间隔,单位毫秒
        prop.put("org.quartz.jobStore.clusterCheckinInterval", clusterCheckinInterval);
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", maxMisfiresToHandleAtATime);
        //最大能忍受的触发超时时间
        prop.put("org.quartz.jobStore.misfireThreshold", misfireThreshold);
        //数据表的前缀,默认QRTZ_
        prop.put("org.quartz.jobStore.tablePrefix", tablePrefix);
        //是否开启悲观锁控制集群中trigger并发
        prop.put("org.quartz.jobStore.acquireTriggersWithinLock", acquireTriggersWithinLock);

        //数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
        prop.put("org.quartz.jobStore.driverDelegateClass", driverDelegateClass);
        //数据库别名 随便取
        prop.put("org.quartz.jobStore.dataSource",dataSourceName);
        //数据库连接池,将其设置为druid
        prop.put("org.quartz.dataSource.qzDS.connectionProvider.class",connectionProviderClass);
        //数据库引擎
        prop.put("org.quartz.dataSource.qzDS.driver",driver);
        //数据库连接
        prop.put("org.quartz.dataSource.qzDS.URL",url);
        //数据库用户
        prop.put("org.quartz.dataSource.qzDS.user",user);
        //数据库密码
        prop.put("org.quartz.dataSource.qzDS.password",password);
        //允许最大连接
        prop.put("org.quartz.dataSource.qzDS.maxConnection",maxConnection);

        factory.setQuartzProperties(prop);

        //支持在JOB实例中注入其他的业务对象
        factory.setJobFactory(jobFactory);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        //这样当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
        factory.setWaitForJobsToCompleteOnShutdown(true);
        //是否覆盖己存在的Job
        factory.setOverwriteExistingJobs(false);
        //QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动
        factory.setStartupDelay(10);
        //使用数据源,自定义数据源(采用项目数据源)
        //在quartz.properties配置文件中,去掉org.quartz.jobStore.dataSource配置
//        factory.setDataSource(dataSource);
        return factory;
    }

    /**
     * 通过SchedulerFactoryBean获取Scheduler的实例
     * @return
     * @throws IOException
     * @throws SchedulerException
     */
    @Bean(name = "scheduler")
    public Scheduler scheduler() throws IOException, SchedulerException {
        Scheduler scheduler = schedulerFactoryBean().getScheduler();
        //全局添加监听器
        //添加SchedulerListener监听器
        scheduler.getListenerManager().addSchedulerListener(simpleSchedulerListener);

        // 添加JobListener, 支持带条件匹配监听器
        scheduler.getListenerManager().addJobListener(simpleJobListener, KeyMatcher.keyEquals(JobKey.jobKey(MaterialConstants.QURATZ_NAME,MaterialConstants.QURATZ_GROUP_NAME)));

        // 添加triggerListener,设置全局监听
        scheduler.getListenerManager().addTriggerListener(simpleTriggerListener, EverythingMatcher.allTriggers());
        return scheduler;
    }
}

5、重新设置 Quartz 数据连接池



import com.alibaba.druid.pool.DruidDataSource;
import org.quartz.SchedulerException;
import org.quartz.utils.ConnectionProvider;

import java.sql.Connection;
import java.sql.SQLException;

/** @Author: 
 * @Description: 3.6、重新设置 Quartz 数据连接池
 * @Date: 15:40 2022/8/10
 * @Param
 * @return
 **/
public class DruidConnectionProvider implements ConnectionProvider {

    /**
     * 常量配置,与quartz.properties文件的key保持一致(去掉前缀),同时提供set方法,Quartz框架自动注入值。
     * @return
     * @throws SQLException
     */

    /**JDBC驱动*/
    public String driver;
    /**JDBC连接串*/
    public String URL;
    /**数据库用户名*/
    public String user;
    /**数据库用户密码*/
    public String password;
    /**数据库最大连接数*/
    public int maxConnection;
    /**数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。*/
    public String validationQuery;

    private boolean validateOnCheckout;

    private int idleConnectionValidationSeconds;

    public String maxCachedStatementsPerConnection;

    private String discardIdleConnectionsSeconds;

    public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;

    public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;

    //Druid连接池
    private DruidDataSource datasource;

    @Override
    public Connection getConnection() throws SQLException {
        return datasource.getConnection();
    }

    @Override
    public void shutdown() throws SQLException {
        datasource.close();
    }

    @Override
    public void initialize() throws SQLException {
        if (this.URL == null) {
            throw new SQLException("DBPool could not be created: DB URL cannot be null");
        }

        if (this.driver == null) {
            throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");
        }

        if (this.maxConnection < 0) {
            throw new SQLException("DBPool maxConnectins could not be created: Max connections must be greater than zero!");
        }

        datasource = new DruidDataSource();
        try{
            datasource.setDriverClassName(this.driver);
        } catch (Exception e) {
            try {
                throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e);
            } catch (SchedulerException e1) {
            }
        }

        datasource.setUrl(this.URL);
        datasource.setUsername(this.user);
        datasource.setPassword(this.password);
        datasource.setMaxActive(this.maxConnection);
        datasource.setMinIdle(1);
        datasource.setMaxWait(0);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CONNECTIONS);

        if (this.validationQuery != null) {
            datasource.setValidationQuery(this.validationQuery);
            if(!this.validateOnCheckout){
                datasource.setTestOnReturn(true);
            }else{
                datasource.setTestOnBorrow(true);
            }
            datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
        }
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getURL() {
        return URL;
    }

    public void setURL(String URL) {
        this.URL = URL;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

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

    public int getMaxConnection() {
        return maxConnection;
    }

    public void setMaxConnection(int maxConnection) {
        this.maxConnection = maxConnection;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

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

    public boolean isValidateOnCheckout() {
        return validateOnCheckout;
    }

    public void setValidateOnCheckout(boolean validateOnCheckout) {
        this.validateOnCheckout = validateOnCheckout;
    }

    public int getIdleConnectionValidationSeconds() {
        return idleConnectionValidationSeconds;
    }

    public void setIdleConnectionValidationSeconds(int idleConnectionValidationSeconds) {
        this.idleConnectionValidationSeconds = idleConnectionValidationSeconds;
    }

    public DruidDataSource getDatasource() {
        return datasource;
    }

    public void setDatasource(DruidDataSource datasource) {
        this.datasource = datasource;
    }

    public String getDiscardIdleConnectionsSeconds() {
        return discardIdleConnectionsSeconds;
    }

    public void setDiscardIdleConnectionsSeconds(String discardIdleConnectionsSeconds) {
        this.discardIdleConnectionsSeconds = discardIdleConnectionsSeconds;
    }
}

创建完成之后,还需要在配置文件中设置一下即可!

org:
  quartz:
    dataSource:
      qzDS:
        connectionProvider:
          class: com.mes.material.quartz.provider.DruidConnectionProvider

6、编写 Job 具体任务类

package com.mes.material.quartz.tfjob;

import com.mes.material.eam.domain.MaterialRequestVo;
import com.mes.material.eam.enums.InterfaceTypeEnum;
import com.mes.material.eam.service.StrategyService;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.TimeUnit;


/**
 * @Author: 
 * @Description: 3.7、编写 Job 具体任务类
 * @Date Create in 15:43 2022/8/10
 * @Modified By:
 */

//此标记用在实现Job的类上面,意思是不允许并发执行,按照我之前的理解是不允许调度框架在同一时刻调用Job类,后来经过测试发现并不是这样,
// 而是Job(任务)的执行时间[比如需要10秒]大于任务的时间间隔[Interval(5秒)],那么默认情况下,调度框架为了能让
//任务按照我们预定的时间间隔执行,会马上启用新的线程执行任务。否则的话会等待任务执行完毕以后再重新执行!(这样会导致任务的执行不是按照我们预先定义的时间间隔执行)
//测试代码,这是官方提供的例子。设定的时间间隔为3秒,但job执行时间是5秒,设置@DisallowConcurrentExecution以后程序会等任务执行完毕以后再去执行,
// 否则会在3秒时再启用新的线程执行

@DisallowConcurrentExecution
public class TfCommandJob implements Job {

    @Autowired
    private StrategyService strategyService;

    private static final Logger log = LoggerFactory.getLogger(TfCommandJob.class);

    private static final String lock_key = "material_from_eam_info";

    private static final String lock_value = "material_from_eam_info_zkaw";

    @Value("${schedule.expire}")
    private long timeOut;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public void execute(JobExecutionContext context) {
        try {
            //分布式锁
            boolean lock = false;
            try {
                //如果返回true,说明key不存在,获取到锁
                lock = redisTemplate.opsForValue().setIfAbsent(lock_key, lock_value);
                log.info("是否获取到锁:" + lock);
                if (lock) {
                    log.info("获取到锁,开启定时任务!");
                    //设置过期时间
                    redisTemplate.expire(lock_key, timeOut, TimeUnit.SECONDS);
                    //调用EAM接口,同步物资数据
                    log.info("调用EAM接口,同步物资数据");
                    String type = InterfaceTypeEnum.autoMaterial.getCode();
                    MaterialRequestVo materialRequestVo = new MaterialRequestVo();
                    materialRequestVo.setType(type);
                    strategyService.render(type).materialInfoMesToRoma(materialRequestVo);
                 } else {
                    log.info("其他系统正在执行此项任务");
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } catch (Throwable throwable) {
                throw new RuntimeException("分布式锁执行发生异常" + throwable.getMessage(), throwable);
            }

        } catch (Exception e) {
            log.error("任务执行失败",e);
        }
    }
}

7、编写 Quartz 服务层接口

package com.mes.material.quartz.service;

import java.util.Map;

/**
 * @Author: 
 * @Description: 3.8、编写 Quartz 服务层接口
 * @Date Create in 14:14 2022/5/16
 * @Modified By:
 */
public interface IQuartzJobService {
    /**
     * 添加任务可以传参数
     * @param clazzName
     * @param jobName
     * @param groupName
     * @param cronExp
     * @param param
     */
    void addJob(String clazzName, String jobName, String groupName, String cronExp, Map<String, Object> param);

    /**
     * 暂停任务
     * @param jobName
     * @param groupName
     */
    void pauseJob(String jobName, String groupName);

    /**
     * 恢复任务
     * @param jobName
     * @param groupName
     */
    void resumeJob(String jobName, String groupName);

    /**
     * 立即运行一次定时任务
     * @param jobName
     * @param groupName
     */
    void runOnce(String jobName, String groupName);

    /**
     * 更新任务
     * @param jobName
     * @param groupName
     * @param cronExp
     * @param param
     */
    void updateJob(String jobName, String groupName, String cronExp, Map<String, Object> param);

    /**
     * 删除任务
     * @param jobName
     * @param groupName
     */
    void deleteJob(String jobName, String groupName);

    /**
     * 启动所有任务
     */
    void startAllJobs();

    /**
     * 暂停所有任务
     */
    void pauseAllJobs();

    /**
     * 恢复所有任务
     */
    void resumeAllJobs();

    /**
     * 关闭所有任务
     */
    void shutdownAllJobs();
}

8、编写 Quartz 服务层接口实现类

package com.mes.material.quartz.service.impl;

import com.mes.material.quartz.service.IQuartzJobService;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * @Author: 
 * @Description:
 * @Date Create in 14:15 2022/5/16
 * @Modified By:
 */

@Service
public class IQuartzJobServiceImpl implements IQuartzJobService {

    private static final Logger log = LoggerFactory.getLogger(IQuartzJobServiceImpl.class);

    @Autowired
    private Scheduler scheduler;

    @Override
    public void addJob(String clazzName, String jobName, String groupName, String cronExp, Map<String, Object> param) {
        try {
            // 启动调度器,默认初始化的时候已经启动
//            scheduler.start();
            //构建job信息
            Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(clazzName);
            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, groupName).build();
            //表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExp);
            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, groupName).withSchedule(scheduleBuilder).build();
            //获得JobDataMap,写入数据
            if (param != null) {
                trigger.getJobDataMap().putAll(param);
            }
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (Exception e) {
            log.error("创建任务失败", e);
        }
    }

    @Override
    public void pauseJob(String jobName, String groupName) {
        try {
            scheduler.pauseJob(JobKey.jobKey(jobName, groupName));
        } catch (SchedulerException e) {
            log.error("暂停任务失败", e);
        }
    }

    @Override
    public void resumeJob(String jobName, String groupName) {
        try {
            scheduler.resumeJob(JobKey.jobKey(jobName, groupName));
        } catch (SchedulerException e) {
            log.error("恢复任务失败", e);
        }
    }

    @Override
    public void runOnce(String jobName, String groupName) {
        try {
            scheduler.triggerJob(JobKey.jobKey(jobName, groupName));
        } catch (SchedulerException e) {
            log.error("立即运行一次定时任务失败", e);
        }
    }

    @Override
    public void updateJob(String jobName, String groupName, String cronExp, Map<String, Object> param) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, groupName);
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (cronExp != null) {
                // 表达式调度构建器
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExp);
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            }
            //修改map
            if (param != null) {
                trigger.getJobDataMap().putAll(param);
            }
            // 按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (Exception e) {
            log.error("更新任务失败", e);
        }
    }

    @Override
    public void deleteJob(String jobName, String groupName) {
        try {
            //暂停、移除、删除
            scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, groupName));
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, groupName));
            scheduler.deleteJob(JobKey.jobKey(jobName, groupName));
        } catch (Exception e) {
            log.error("删除任务失败", e);
        }
    }

    @Override
    public void startAllJobs() {
        try {
            scheduler.start();
        } catch (Exception e) {
            log.error("开启所有的任务失败", e);
        }
    }

    @Override
    public void pauseAllJobs() {
        try {
            scheduler.pauseAll();
        } catch (Exception e) {
            log.error("暂停所有任务失败", e);
        }
    }

    @Override
    public void resumeAllJobs() {
        try {
            scheduler.resumeAll();
        } catch (Exception e) {
            log.error("恢复所有任务失败", e);
        }
    }

    @Override
    public void shutdownAllJobs() {
        try {

            if (!scheduler.isShutdown()) {
                // 需谨慎操作关闭scheduler容器
                // scheduler生命周期结束,无法再 start() 启动scheduler
                scheduler.shutdown(true);
            }
        } catch (Exception e) {
            log.error("关闭所有的任务失败", e);
        }
    }
}

9、编写 contoller 服务

先编写一个调用实体类

package com.mes.material.quartz.config;

import lombok.Data;

import java.io.Serializable;
import java.util.Map;

/**
 * @Author: 
 * @Description:
 * @Date Create in 14:16 2022/5/16
 * @Modified By:
 */
@Data
public class QuartzConfigDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 任务名称
     */
    private String jobName;

    /**
     * 任务所属组
     */
    private String groupName;

    /**
     * 任务执行类
     */
    private String jobClass;

    /**
     * 任务调度时间表达式
     */
    private String cronExpression;

    /**
     * 附加参数
     */
    private Map<String, Object> param;
}

web服务接口

@RestController
@RequestMapping("/test")
public class TestController {
 
    private static final Logger log = LoggerFactory.getLogger(TestController.class);
 
    @Autowired
    private QuartzJobService quartzJobService;
 
    /**
     * 添加新任务
     * @param configDTO
     * @return
     */
    @RequestMapping("/addJob")
    public Object addJob(@RequestBody QuartzConfigDTO configDTO) {
        quartzJobService.addJob(configDTO.getJobClass(), configDTO.getJobName(), configDTO.getGroupName(), configDTO.getCronExpression(), configDTO.getParam());
        return HttpStatus.OK;
    }
 
    /**
     * 暂停任务
     * @param configDTO
     * @return
     */
    @RequestMapping("/pauseJob")
    public Object pauseJob(@RequestBody QuartzConfigDTO configDTO) {
        quartzJobService.pauseJob(configDTO.getJobName(), configDTO.getGroupName());
        return HttpStatus.OK;
    }
 
    /**
     * 恢复任务
     * @param configDTO
     * @return
     */
    @RequestMapping("/resumeJob")
    public Object resumeJob(@RequestBody QuartzConfigDTO configDTO) {
        quartzJobService.resumeJob(configDTO.getJobName(), configDTO.getGroupName());
        return HttpStatus.OK;
    }
 
    /**
     * 立即运行一次定时任务
     * @param configDTO
     * @return
     */
    @RequestMapping("/runOnce")
    public Object runOnce(@RequestBody QuartzConfigDTO configDTO) {
        quartzJobService.runOnce(configDTO.getJobName(), configDTO.getGroupName());
        return HttpStatus.OK;
    }
 
    /**
     * 更新任务
     * @param configDTO
     * @return
     */
    @RequestMapping("/updateJob")
    public Object updateJob(@RequestBody QuartzConfigDTO configDTO) {
        quartzJobService.updateJob(configDTO.getJobName(), configDTO.getGroupName(), configDTO.getCronExpression(), configDTO.getParam());
        return HttpStatus.OK;
    }
 
    /**
     * 删除任务
     * @param configDTO
     * @return
     */
    @RequestMapping("/deleteJob")
    public Object deleteJob(@RequestBody QuartzConfigDTO configDTO) {
        quartzJobService.deleteJob(configDTO.getJobName(), configDTO.getGroupName());
        return HttpStatus.OK;
    }
 
    /**
     * 启动所有任务
     * @return
     */
    @RequestMapping("/startAllJobs")
    public Object startAllJobs() {
        quartzJobService.startAllJobs();
        return HttpStatus.OK;
    }
 
    /**
     * 暂停所有任务
     * @return
     */
    @RequestMapping("/pauseAllJobs")
    public Object pauseAllJobs() {
        quartzJobService.pauseAllJobs();
        return HttpStatus.OK;
    }
 
    /**
     * 恢复所有任务
     * @return
     */
    @RequestMapping("/resumeAllJobs")
    public Object resumeAllJobs() {
        quartzJobService.resumeAllJobs();
        return HttpStatus.OK;
    }
 
    /**
     * 关闭所有任务
     * @return
     */
    @RequestMapping("/shutdownAllJobs")
    public Object shutdownAllJobs() {
        quartzJobService.shutdownAllJobs();
        return HttpStatus.OK;
    }
 
}

10、调用quartz

常量值

 

 11、注册监听器(选用)

  • 创建任务调度监听器
package com.mes.material.quartz.listener;

import org.quartz.*;
import org.quartz.listeners.SchedulerListenerSupport;
import org.springframework.stereotype.Component;

/**
 * @Author: 
 * @Description: 注册监听器(选用)
 * @Date Create in 14:47 2022/5/16
 * @Modified By:
 */

@Component
public class SimpleSchedulerListener extends SchedulerListenerSupport {

    @Override
    public void jobScheduled(Trigger trigger) {
        System.out.println("任务被部署时被执行");
    }

    @Override
    public void jobUnscheduled(TriggerKey triggerKey) {
        System.out.println("任务被卸载时被执行");
    }

    @Override
    public void triggerFinalized(Trigger trigger) {
        System.out.println("任务完成了它的使命,光荣退休时被执行");
    }

    @Override
    public void triggerPaused(TriggerKey triggerKey) {
        System.out.println(triggerKey + "(一个触发器)被暂停时被执行");
    }

    @Override
    public void triggersPaused(String triggerGroup) {
        System.out.println(triggerGroup + "所在组的全部触发器被停止时被执行");
    }

    @Override
    public void triggerResumed(TriggerKey triggerKey) {
        System.out.println(triggerKey + "(一个触发器)被恢复时被执行");
    }

    @Override
    public void triggersResumed(String triggerGroup) {
        System.out.println(triggerGroup + "所在组的全部触发器被回复时被执行");
    }

    @Override
    public void jobAdded(JobDetail jobDetail) {
        System.out.println("一个JobDetail被动态添加进来");
    }

    @Override
    public void jobDeleted(JobKey jobKey) {
        System.out.println(jobKey + "被删除时被执行");
    }

    @Override
    public void jobPaused(JobKey jobKey) {
        System.out.println(jobKey + "被暂停时被执行");
    }

    @Override
    public void jobsPaused(String jobGroup) {
        System.out.println(jobGroup + "(一组任务)被暂停时被执行");
    }

    @Override
    public void jobResumed(JobKey jobKey) {
        System.out.println(jobKey + "被恢复时被执行");
    }

    @Override
    public void jobsResumed(String jobGroup) {
        System.out.println(jobGroup + "(一组任务)被恢复时被执行");
    }

    @Override
    public void schedulerError(String msg, SchedulerException cause) {
        System.out.println("出现异常" + msg + "时被执行");
        cause.printStackTrace();
    }

    @Override
    public void schedulerInStandbyMode() {
        System.out.println("scheduler被设为standBy等候模式时被执行");
    }

    @Override
    public void schedulerStarted() {
        System.out.println("scheduler启动时被执行");
    }

    @Override
    public void schedulerStarting() {
        System.out.println("scheduler正在启动时被执行");
    }

    @Override
    public void schedulerShutdown() {
        System.out.println("scheduler关闭时被执行");
    }

    @Override
    public void schedulerShuttingdown() {
        System.out.println("scheduler正在关闭时被执行");
    }

    @Override
    public void schedulingDataCleared() {
        System.out.println("scheduler中所有数据包括jobs, triggers和calendars都被清空时被执行");
    }
}
  • 创建任务触发监听器
package com.mes.material.quartz.listener;

import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.listeners.TriggerListenerSupport;
import org.springframework.stereotype.Component;

/**
 * @Author: 
 * @Description: 任务触发监听器
 * @Date Create in 14:48 2022/5/16
 * @Modified By:
 */
@Component
public class SimpleTriggerListener extends TriggerListenerSupport {

    /**
     * Trigger监听器的名称
     * @return
     */
    @Override
    public String getName() {
        return "mySimpleTriggerListener";
    }

    /**
     * Trigger被激发 它关联的job即将被运行
     * @param trigger
     * @param context
     */
    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        System.out.println("myTriggerListener.triggerFired()");
    }

    /**
     * Trigger被激发 它关联的job即将被运行, TriggerListener 给了一个选择去否决 Job 的执行,如果返回TRUE 那么任务job会被终止
     * @param trigger
     * @param context
     * @return
     */
    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        System.out.println("myTriggerListener.vetoJobExecution()");
        return false;
    }

    /**
     * 当Trigger错过被激发时执行,比如当前时间有很多触发器都需要执行,但是线程池中的有效线程都在工作,
     * 那么有的触发器就有可能超时,错过这一轮的触发。
     * @param trigger
     */
    @Override
    public void triggerMisfired(Trigger trigger) {
        System.out.println("myTriggerListener.triggerMisfired()");
    }

    /**
     * 任务完成时触发
     * @param trigger
     * @param context
     * @param triggerInstructionCode
     */
    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
        System.out.println("myTriggerListener.triggerComplete()");
    }
}
  • 创建任务执行监听器
package com.mes.material.quartz.listener;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.listeners.JobListenerSupport;
import org.springframework.stereotype.Component;

/**
 * @Author: 
 * @Description: 任务执行监听器
 * @Date Create in 14:49 2022/5/16
 * @Modified By:
 */

@Component
public class SimpleJobListener extends JobListenerSupport {


    /**
     * job监听器名称
     * @return
     */
    @Override
    public String getName() {
        return "mySimpleJobListener";
    }

    /**
     * 任务被调度前
     * @param context
     */
    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        System.out.println("simpleJobListener监听器,准备执行:"+context.getJobDetail().getKey());
    }

    /**
     * 任务调度被拒了
     * @param context
     */
    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("simpleJobListener监听器,取消执行:"+context.getJobDetail().getKey());
    }

    /**
     * 任务被调度后
     * @param context
     * @param jobException
     */
    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        System.out.println("simpleJobListener监听器,执行结束:"+context.getJobDetail().getKey());
    }
}
  • 最后,将监听器注册到Scheduler
@Autowired
private SimpleSchedulerListener simpleSchedulerListener;
 
@Autowired
private SimpleJobListener simpleJobListener;
 
@Autowired
private SimpleTriggerListener simpleTriggerListener;
 
@Bean(name = "scheduler")
public Scheduler scheduler() throws IOException, SchedulerException {
    Scheduler scheduler = schedulerFactoryBean().getScheduler();
    //全局添加监听器
    //添加SchedulerListener监听器
    scheduler.getListenerManager().addSchedulerListener(simpleSchedulerListener);
 
    // 添加JobListener, 支持带条件匹配监听器
    scheduler.getListenerManager().addJobListener(simpleJobListener, KeyMatcher.keyEquals(JobKey.jobKey("myJob", "myGroup")));
 
    // 添加triggerListener,设置全局监听
    scheduler.getListenerManager().addTriggerListener(simpleTriggerListener, EverythingMatcher.allTriggers());
    return scheduler;
}

本文主要围绕springboot + quartz + postgresql实现持久化分布式调度进行介绍,所有的代码功能,都测试过。 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot整合Quartz 实现分布式定时任务调度 的相关文章

随机推荐

  • VTK笔记-裁剪分割-几何裁剪-vtkClipPolyData

    什么是裁剪分割 VTK加载二维数据和三维数据 xff0c 我们通常观察到的是三视图图像 xff0c 或者是vtkImageReslice获取到的转换矩阵对应的观察点三视图或者是斜平面图像 xff0c 想要看到图像中间部分的渲染结果就要借助
  • github开源项目Bringing-Old-Photos-Back-to-Life实战

    Github项目 xff1a https github com microsoft Bringing Old Photos Back to Life 1 环境 win11 python 3 8 8 torch 1 9 1 torchvisi
  • sapjco3.jar在maven项目中的打包问题

    sapjco3 jar的打包问题 前几天做一个跟SAP系统有关的小功能时 xff0c 项目用到了sapjco3 jar的jar包 xff0c 项目打包部署后报错 com hand sapjco 3 system basedir src ma
  • 记录:c#中使用Selenium之一 使用chrome驱动手机模式浏览

    1 最近研究c 使用各种模拟浏览器的工具 xff0c 下面是Selenium的使用记录 1 xff09 首先使用Nuget搜索以下依赖库 2 xff09 我使用的是手机模式的浏览方式 下面为手机浏览模式设置的代码 xff0c 以移动端百度搜
  • 解决VS平台迁移时报错error MSB8020:The build tools for v141

    将在VS2017上编译的程序放到VS2013中 xff0c 报错 xff1a error MSB8020 The build tools for v141 Platform Toolset 61 39 v141 39 cannot be f
  • C++ 中的char型变量

    最简单的字符数据类型是 char 数据类型 该类型的变量只能容纳一个字符 xff0c 而且在大多数系统上 xff0c 只使用一个字节的内存 以下示例即声明了一个名为 letter 的 char 变量 请注意 xff0c 这里的字符常数就是赋
  • linux下DISPLAY和xhost + 作用

    在Linux Unix类操作系统上 DISPLAY用来设置将图形显示到何处 直接登陆图形界面或者登陆命令行界面后使用startx启动图形 DISPLAY环境变量将自动设置为 0 0 此时可以打开终端 输出图形程序的名称 比如xclock 来
  • 配置 maven 编译的 JDK 版本

    两种方式 xff1a 一 可以修改 MAVEN 的 setting xml 文件 xff0c 统一修改 lt profiles gt lt profile gt lt id gt jdk 1 6 lt id gt lt activation
  • 利用redis的setIfAbsent()方法实现分布式锁

    再集群环境中 xff0c 存在定时任务多次执行 xff0c 浪费资源 xff0c 那么如何避免这种情况呢 xff0c 下面就说明一下如何利用一个注解解决问题 xff0c 利用切面配合redis可以简单实现分布式锁 xff0c 解决定时任务重
  • Virtualbox主机和虚拟机之间文件夹共享及双向拷贝(win7——centos7)

    一 双向拷贝 xff1a 然后 xff0c 还需要通过virtualbox上安装一个增强的工具 此时 xff0c 会在centos上安装一些工具 xff1a 鼠标自动在宿主机 虚拟机之间移出 同时 xff0c 在centos上会出现一个安装
  • Record something about DL

    这篇文章算是DL实践杂谈吧 xff0c 主要是想把自己模型调优和复现算法遇到的一些坑总结一下 xff08 里面的一行字可能是我当时花费了一周甚至更长时间得到的总结 xff09 xff0c 希望能对读者有所帮助 一 熟悉数据 模型是数据的浓缩
  • Image captioning任务常用的评价指标计算

    BLEU ACL 2002Meteor AMTA 2004ROUGE L ACL 2004CIDEr CVPR 2015SPICE ECCV 2016
  • Image captioning评价方法之BLEU (bilingual evaluation understudy)

    文章地址 xff1a BLEU a Method for Automatic Evaluation of Machine Translation 代码地址 非官方 xff1a https github com tylin coco capt
  • Image captioning评价方法之Meteor

    项目地址 xff1a http www cs cmu edu alavie METEOR 代码地址 xff08 非官方实现 xff0c 实现的是项目地址中的1 5版本 xff09 xff1a https github com tylin c
  • Image captioning评价方法之ROUGE-L

    文章地址 xff1a ROUGE A Package for Automatic Evaluation of Summaries 代码地址 非官方 xff1a https github com tylin coco caption 文章由U
  • Image captioning评价方法之CIDEr

    文章地址 xff1a CIDEr Consensus based Image Description Evaluation 代码地址 xff08 非官方 xff0c 且代码实现的是CIDEr D xff09 xff1a https gith
  • Image captioning评价方法之SPICE

    项目地址 xff1a https panderson me spice 上述的项目地址包含了论文地址和代码地址 该方法是由The Australian National University和Macquarie University联合发表
  • R3DS Wrap基本使用方法

    中文的R3DS Wrap软件的教程较少 xff0c 最近刚好实操了一遍 xff0c 特此记录下来 为了描述方便 xff0c 下面将R3DS Wrap简称Wrap 软件官网 xff1a https www russian3dscanner c
  • docker使用入门简介

    一 什么是docker xff1f https www docker com resources what container 使用docker时有两个重要概念 xff0c 一个是镜像 xff08 images xff09 xff0c 一个
  • SpringBoot整合Quartz 实现分布式定时任务调度

    一 Quartz 集群架构 Quartz 是 Java 领域最著名的开源任务调度工具 在上篇文章中 xff0c 我们详细的介绍了 Quartz 的单体应用实践 xff0c 如果只在单体环境中应用 xff0c Quartz 未必是最好的选择