SpringBoot与quartz框架实现分布式定时任务

前言

quartz的分布式调度策略是以数据库为边界资源的一种异步策略。各个调度器都遵守一个基于数据库锁的操作规则从而保证了操作的唯一性。

在quartz的集群解决方案里有张表scheduler_locks,quartz采用了悲观锁的方式对triggers表进行行加锁,以保证任务同步的正确性。一旦某一个节点上面的线程获取了该锁,那么这个Job就会在这台机器上被执行,同时这个锁就会被这台机器占用。同时另外一台机器也会想要触发这个任务,但是锁已经被占用了,就只能等待,直到这个锁被释放。

一、介绍

1.Quartz 核心概念

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

  1. Job: 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context);
  1. JobDetail:  表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
  2. Trigger: 代表一个调度参数的配置,什么时候去调。
  3. Scheduler: 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

2. 原理图

在这里插入图片描述

二、使用步骤

1. 引入依赖

代码如下(示例):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wyc</groupId>
    <artifactId>quartz</artifactId>
    <version>1.0-SNAPSHOT</version>


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

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

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

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

        <!-- Druid是阿里巴巴推出的国产数据库连接池,据网上测试对比,比目前的DBCP或C3P0数据库连接池性能更好-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!--google工具类-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 在项目中添加quartz.properties文件(不添加该文件该框架会加载自带的properties文件)

# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.

#使用自己的配置文件
org.quartz.jobStore.useProperties:true

#默认或是自己改名字都行
org.quartz.scheduler.instanceName: DefaultQuartzScheduler

#如果使用集群,instanceId必须唯一,设置成AUTO
org.quartz.scheduler.instanceId = AUTO

org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
#============================================================================
# Configure JobStore
#============================================================================
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

#存储方式使用JobStoreTX,也就是数据库
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#数据库中quartz表的表名前缀
org.quartz.jobStore.tablePrefix:qrtz_
org.quartz.jobStore.dataSource:qzDS
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
org.quartz.jobStore.isClustered = true

#============================================================================
# Configure Datasources
#============================================================================
#配置数据库源(org.quartz.dataSource.qzDS.maxConnections: c3p0配置的是有s的,druid数据源没有s)
org.quartz.dataSource.qzDS.connectionProvider.class:com.cbw.quartz02.util.DruidConnectionProvider
org.quartz.dataSource.qzDS.driver: com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8
org.quartz.dataSource.qzDS.user: root
org.quartz.dataSource.qzDS.password: 123
org.quartz.dataSource.qzDS.maxConnection: 10

在依赖中可以看到引入了两种连接池,这两种连接池是可选择的。quartz框架默认的选择C3P0连接池,如果想要更换连接池就需要配置文件,如上进行修改。

3.在数据库中创建quartz相关的表(建议与业务隔离库)

1)进入quartz的官网http://www.quartz-scheduler.org/,点击Downloads,下载后在目录\docs\dbTables下有常用数据库创建quartz表的脚本。
例如:“tables_mysql.sql”
tables_mysql.sql 、tables_mysql_innodb.sql
上述两者所有的数据库引擎不一样,根据需要进行选择。导入之后,数据会出现下列几张表,但没有数据。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2)本博客最后项目中包含数据库脚本。
tables_mysql_innodb.sql

#
# In your Quartz properties file, you'll need to set 
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
#
# By: Ron Cordell - roncordell
#  I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.

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(200) NOT NULL,
JOB_GROUP VARCHAR(200) 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(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) 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(200) 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(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) 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(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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(200) 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(200) 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(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) 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(200) NULL,
JOB_GROUP VARCHAR(200) 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(200) 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; 

4.项目结构

在这里插入图片描述
包名解释:

  1. command:此包中包含实现了springboot中CommandLineRunner的JobCommandLine,应用启动后会执行CommandLineRunner类中run方法中的代码,利用这个特性可以在应用首次启动时执行定时任务。
  2. config:此项目采用的是JavaConfig+application.yml的配置方式,此包中是所有的JavaConfig配置类。其中DataSourceConfig配置类如果存在就不会去读quratz.properties中的数据源配置。
  3. dao:数据库操作层。
  4. entity:实体类
  5. enums: 枚举层。
  6. quartz: 包含各种job和支持。
  7. mapper:mapper层。
  8. utils: 各种工具类。

5.代码配置

1) 多数据源配置 - DataSourceConfig:
package com.wyc.demo.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:25
 * @description:
 */
@Configuration
public class DataSourceConfig {

    @Primary
    @Bean("quartzDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.quartz")
    public DataSource quartzDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean("demoDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.demo")
    public DataSource accountDataSource() {
        return DataSourceBuilder.create().build();
    }

}
2) 配置文件 - application.yml:
server:
  port: 8080

spring:
#---------------------kafka配置---------------------
#---------------------数据源---------------------
  datasource:
    druid:
      quartz:
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
        username: wyc
        password: 1111
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          initial-size: 20
          min-idle: 5
          max-active: 50
          max-wait: 60000
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 300000
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          pool-prepared-statements: false
          max-pool-prepared-statement-per-connection-size: 20
      demo:
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
        username: wyc
        password: 1111
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          initial-size: 20
          min-idle: 5
          max-active: 50
          max-wait: 60000
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 300000
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          pool-prepared-statements: false
          max-pool-prepared-statement-per-connection-size: 20

#---------------------mybatis---------------------
mybatis:
  credit:
    mapper-locations: classpath:mappers/demo/*.xml
  quartz:
    mapper-locations: classpath:mappers/quartz/*.xml

#---------------------日志---------------------
logging:
  level:
    root: info
/src/main/resources/mappers/demo/DemoMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wyc.demo.dao.demo">


</mapper>
/src/main/resources/mappers/quartz/DemoMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wyc.demo.dao.quartz">


</mapper>
3) Quartz配置 - QuartzConfig:
@Configuration
@ConditionalOnClass(QuartzScheduler.class)
@ConditionalOnProperty(prefix = "quartz", name = "enabled", havingValue = "true", matchIfMissing = true)
public class QuartzConfig {

    @Autowired(required = false)
    private List<CronTrigger> triggers = new ArrayList<>();

    @Bean
    @ConditionalOnMissingBean(name = "schedulerFactory")
    public SchedulerFactoryBean schedulerFactory(DataSource quartzDataSource, JobFactory jobFactory, DataSourceTransactionManager quartzTransactionManager) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setDataSource(quartzDataSource);
        bean.setTransactionManager(quartzTransactionManager);
        bean.setApplicationContextSchedulerContextKey("applicationContextKey");
//        bean.setConfigLocation(new ClassPathResource("quartz.properties"));
        bean.setJobFactory(jobFactory);
        bean.setTriggers(triggers.toArray(new CronTrigger[]{}));
        return bean;
    }

    @Bean
    @ConditionalOnMissingBean(JobFactory.class)
    public JobFactory jobFactory() {
        return new AutowiringSpringBeanJobFactory();
    }
    
    @Bean
    @ConditionalOnMissingBean(ScheduleJobService.class)
    public ScheduleJobService scheduleJobService() {
        return new ScheduleJobService();
    }

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor bean = new ThreadPoolTaskExecutor();
        bean.setCorePoolSize(5);// 核心线程数,默认为1
        bean.setMaxPoolSize(50);// 最大线程数,默认为Integer.MAX_VALUE
        bean.setQueueCapacity(1000);// 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE
        bean.setKeepAliveSeconds(300);// 线程池维护线程所允许的空闲时间,默认为60s
        // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
        // AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常
        // CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度
        // DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行
        // DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行
        bean.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return bean;
    }

}

代码解释:

  1. schedulerFactory方法:注入schedulerFactory。
  2. jobFactory方法:注入AutowiringSpringBeanJobFactory。
  3. scheduleJobService方法:注入ScheduleJobService。
  4. threadPoolTaskExecutor方法:注入ThreadPoolTaskExecutor(线程池任务执行器)。
4) 多数据源 Mybatis配置(其一) - DemoMybatisConfig:
package com.wyc.demo.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:25
 * @description:
 */
@Configuration
@MapperScan(value = "com.wyc.demo.dao.demo", sqlSessionTemplateRef = "demoSqlSessionTemplate")
public class DemoMybatisConfig {

    @Bean(name = "demoTransactionManager")
    public DataSourceTransactionManager adminTransactionManager(@Qualifier("demoDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "demoSqlSessionFactory")
    public SqlSessionFactory adminSqlSessionFactory(@Qualifier("demoDataSource") DataSource dataSource, @Value("${mybatis.demo.mapper-locations}") Resource[] mappers) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(mappers);
        return factoryBean.getObject();
    }

    @Bean(name = "demoSqlSessionTemplate")
    public SqlSessionTemplate adminSqlSessionTemplate(@Qualifier("demoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

6.开发流程

1) 多数据源配置 - DataSourceConfig:
@Configuration
public class DataSourceConfig {

    @Primary
    @Bean("quartzDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.quartz")
    public DataSource quartzDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean("demoDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.demo")
    public DataSource accountDataSource() {
        return DataSourceBuilder.create().build();
    }

}
2) 配置文件 - application.yml:
spring:
#---------------------kafka配置---------------------
#---------------------数据源---------------------
  datasource:
    druid:
      quartz:
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
        username: wyc
        password: 1111
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          initial-size: 20
          min-idle: 5
          max-active: 50
          max-wait: 60000
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 300000
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          pool-prepared-statements: false
          max-pool-prepared-statement-per-connection-size: 20
      demo:
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
        username: wyc
        password: 1111
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          initial-size: 20
          min-idle: 5
          max-active: 50
          max-wait: 60000
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 300000
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          pool-prepared-statements: false
          max-pool-prepared-statement-per-connection-size: 20
3) Quartz配置 - QuartzConfig:
package com.wyc.demo.config;

import com.wyc.demo.quartz.support.service.ScheduleJobService;
import com.wyc.demo.quartz.support.spring.AutowiringSpringBeanJobFactory;
import org.quartz.CronTrigger;
import org.quartz.core.QuartzScheduler;
import org.quartz.spi.JobFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:25
 * @description:
 */
@Configuration
@ConditionalOnClass(QuartzScheduler.class)
@ConditionalOnProperty(prefix = "quartz", name = "enabled", havingValue = "true", matchIfMissing = true)
public class QuartzConfig {

    @Autowired(required = false)
    private List<CronTrigger> triggers = new ArrayList<>();

    @Bean
    @ConditionalOnMissingBean(name = "schedulerFactory")
    public SchedulerFactoryBean schedulerFactory(DataSource quartzDataSource, JobFactory jobFactory, DataSourceTransactionManager quartzTransactionManager) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setDataSource(quartzDataSource);
        bean.setTransactionManager(quartzTransactionManager);
        bean.setApplicationContextSchedulerContextKey("applicationContextKey");
//        bean.setConfigLocation(new ClassPathResource("quartz.properties"));
        bean.setJobFactory(jobFactory);
        bean.setTriggers(triggers.toArray(new CronTrigger[]{}));
        return bean;
    }

    @Bean
    @ConditionalOnMissingBean(JobFactory.class)
    public JobFactory jobFactory() {
        return new AutowiringSpringBeanJobFactory();
    }
    
    @Bean
    @ConditionalOnMissingBean(ScheduleJobService.class)
    public ScheduleJobService scheduleJobService() {
        return new ScheduleJobService();
    }

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor bean = new ThreadPoolTaskExecutor();
        bean.setCorePoolSize(5);// 核心线程数,默认为1
        bean.setMaxPoolSize(50);// 最大线程数,默认为Integer.MAX_VALUE
        bean.setQueueCapacity(1000);// 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE
        bean.setKeepAliveSeconds(300);// 线程池维护线程所允许的空闲时间,默认为60s
        // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
        // AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常
        // CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度
        // DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行
        // DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行
        bean.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return bean;
    }

}

代码解释:

  1. schedulerFactory方法:注入schedulerFactory。
  2. jobFactory方法:注入AutowiringSpringBeanJobFactory。
  3. scheduleJobService方法:注入ScheduleJobService。
  4. threadPoolTaskExecutor方法:注入ThreadPoolTaskExecutor(线程池任务执行器)。
QuartzMybatisConfig
package com.wyc.demo.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:25
 * @description:
 */
@Configuration
@MapperScan(value = "com.aisino.social.credit.quartz.dao", sqlSessionTemplateRef = "quartzSqlSessionTemplate")
public class QuartzMybatisConfig {

    @Bean(name = "quartzTransactionManager")
    public DataSourceTransactionManager adminTransactionManager(@Qualifier("quartzDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "quartzSqlSessionFactory")
    public SqlSessionFactory adminSqlSessionFactory(@Qualifier("quartzDataSource") DataSource dataSource, @Value("${mybatis.quartz.mapper-locations}") Resource[] mappers) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(mappers);
        return factoryBean.getObject();
    }

    @Bean(name = "quartzSqlSessionTemplate")
    public SqlSessionTemplate adminSqlSessionTemplate(@Qualifier("quartzSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}
4) 多数据源 Mybatis配置(其一) - DemoMybatisConfig:
@Configuration
@MapperScan(value = "com.wyc.demo.dao.demo", sqlSessionTemplateRef = "demoSqlSessionTemplate")
public class DemoMybatisConfig {

    @Bean(name = "demoTransactionManager")
    public DataSourceTransactionManager adminTransactionManager(@Qualifier("demoDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "demoSqlSessionFactory")
    public SqlSessionFactory adminSqlSessionFactory(@Qualifier("demoDataSource") DataSource dataSource, @Value("${mybatis.demo.mapper-locations}") Resource[] mappers) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(mappers);
        return factoryBean.getObject();
    }

    @Bean(name = "demoSqlSessionTemplate")
    public SqlSessionTemplate adminSqlSessionTemplate(@Qualifier("demoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

6.开发流程

1) JobCommandLine:
package com.wyc.demo.command;

import com.wyc.demo.quartz.job.DemoJob;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author: wangyuanchen
 * @date: 2020-10-30 08:54
 * @description:
 */
@Component
public class JobCommandLine implements CommandLineRunner {

    @Resource
    private DemoJob demoJob;

    @Override
    public void run(String... args) throws Exception {
        demoJob.startJob();
    }
}

代码解释:

  1. 此类(JobCommandLine)实现了SpringBoot中的CommandLineRunner接口,这是一个函数式接口,应用启动时会执行其中的run方法,可以搭配定时任务使用。
  2. run方法中的demoJob.startJob() 执行的业务数据源。
  3. 进入到startJob() 方法中,进入到了SimpleAbstractJob类。
2) Job:
public interface Job {
    void execute(JobExecutionContext var1) throws JobExecutionException;
}

代码解释:

  1. 此接口来自 package org.quartz;,这就是quartz的四大核心概念之一,这个接口里只有一个方法,就是 execute方法,方法体里的内容就是定时任务的执行程序,需要我们实现该接口来添加。(该方法很重要,下面也有讲述)
3) CommonAbstractJob:
package com.wyc.demo.quartz.support;

import com.wyc.demo.entity.ScheduleJob;
import com.wyc.demo.enums.JobGroupType;
import com.wyc.demo.quartz.support.service.ScheduleJobService;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:41
 * @description:不可直接继承此类,参考 {@link SimpleAbstractJob}
 */
abstract class CommonAbstractJob implements Job {

    @Resource
    protected ScheduleJobService scheduleJobService;
    private static final SimpleDateFormat CRON_FORMAT = new SimpleDateFormat("ss mm HH dd MM ? yyyy");

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 获取JobGroup
     * @return
     */
    public abstract JobGroupType getJobGroup();

    /**
     * 获取cron表达式 
     *
     * @param date
     * @return
     */
    public String parseCronExpression(Date date) {
        return CRON_FORMAT.format(date);
    }

    protected void enableSchedule(ScheduleJob job, JobDataMap jobDataMap) throws Exception {
        scheduleJobService.enableSchedule(job, jobDataMap);
    }

    public String getJobGroupString(){
        return getJobGroup().getCode();
    }

}

代码解释:

  1. 此类是一个抽象类,继承Job接口,不过并没有实现 execute方法,那它的作用是什么呢?在CommonAbstractJob里,注入了一个ScheduleJobService,在enableSchedule方法中,执行了:
scheduleJobService.enableSchedule(job, jobDataMap);

这个方法是什么意思呢?进入到ScheduleJobService中看一下便知晓了。

4) ScheduleJobService:
package com.wyc.demo.quartz.support.listener;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.quartz.SchedulerException;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 15:40
 * @description:用于立即触发的任务执行后取消定时器
 */
public class RemoveAfterRunListener implements JobListener {

    private int state = 0;

    @Override
    public String getName() {
        return RemoveAfterRunListener.class.getName();
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext arg0) {
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext arg0) {
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException arg1) {
        try {
            state = 1;
            context.getScheduler().deleteJob(context.getJobDetail().getKey());
        } catch (SchedulerException e) {
            throw new RuntimeException();
        }
    }

    public int getState() {
        return state;
    }

}
package com.wyc.demo.quartz.support.spring;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 15:38
 * @description:
 */
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private AutowireCapableBeanFactory autowireCapableBeanFactory;

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        Object job = applicationContext.getBean(bundle.getJobDetail().getJobClass());
        if (job == null) {
            job = super.createJobInstance(bundle);
            autowireCapableBeanFactory.autowireBean(job);
        }
        return job;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
    }

}
package com.wyc.demo.quartz.support.service;

import com.google.common.collect.Sets;
import com.wyc.demo.entity.ScheduleJob;
import com.wyc.demo.quartz.support.listener.RemoveAfterRunListener;
import org.quartz.*;
import org.quartz.impl.matchers.KeyMatcher;
import org.quartz.impl.triggers.CalendarIntervalTriggerImpl;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.quartz.impl.triggers.DailyTimeIntervalTriggerImpl;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 15:42
 * @description:
 */
public class ScheduleJobService {

    @Autowired
    @Qualifier("schedulerFactory")
    private Scheduler scheduler;

    /**
     * 启用定时任务或重设定时任务的触发时间
     *
     * @param job
     * @param jobDataMap
     * @throws Exception
     */
    public void enableSchedule(ScheduleJob job, JobDataMap jobDataMap) throws Exception {
        if (job == null) {
            return;
        }
        JobDetail jobDetail = JobBuilder.newJob(job.getJobExecuteClass())
                .withIdentity(job.getJobName(), job.getJobGroup().getCode())
                .withDescription(job.getJobGroup().getDesc())
                .build();
        if (jobDataMap != null) {
            jobDetail.getJobDataMap().putAll(jobDataMap);
        }
        //表达式调度构建器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
        //按新的cronExpression表达式构建一个新的trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(job.getTriggerName(), job.getJobGroup().getCode())
                .withSchedule(scheduleBuilder)
                .withDescription(job.getJobGroup().getDesc())
                .build();
        Trigger exists = scheduler.getTrigger(trigger.getKey());
        if (exists != null) {
            if (exists instanceof CronTriggerImpl) {
                ((CronTriggerImpl) trigger).setPreviousFireTime(exists.getPreviousFireTime());
            } else if (exists instanceof CalendarIntervalTriggerImpl) {
                ((CalendarIntervalTriggerImpl) trigger).setPreviousFireTime(exists.getPreviousFireTime());
            } else if (exists instanceof DailyTimeIntervalTriggerImpl) {
                ((DailyTimeIntervalTriggerImpl) trigger).setPreviousFireTime(exists.getPreviousFireTime());
            } else if (exists instanceof SimpleTriggerImpl) {
                ((SimpleTriggerImpl) trigger).setPreviousFireTime(exists.getPreviousFireTime());
            }
        }
        scheduler.scheduleJob(jobDetail, Sets.newHashSet(trigger), true);
    }

    /**
     * 删除定时任务
     *
     * @param jobName
     * @param jobGroup
     * @throws Exception
     */
    public void removeSchedule(String jobName, String jobGroup) throws Exception {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        scheduler.pauseJob(jobKey);
        scheduler.deleteJob(jobKey);
    }

    /**
     * 删除定时任务
     *
     * @param keys:jobGroup.jobName
     * @throws Exception
     */
    public void removeSchedule(String keys) throws Exception {
        String[] arr = keys.split("[.]");
        String jobName = arr[1];
        String jobGroup = arr[0];
        removeSchedule(jobName, jobGroup);
    }

    /**
     * 立即执行定时任务
     *
     * @param jobName
     * @param jobGroup
     * @param delete
     * @param block
     * @throws Exception
     */
    public void execSchedule(String jobName, String jobGroup, JobDataMap jobDataMap, boolean delete, boolean block) throws Exception {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        scheduler.triggerJob(jobKey, jobDataMap);
        RemoveAfterRunListener afterExecListener = new RemoveAfterRunListener();
        if (delete) {//如果要执行完后立即取消定时器
            scheduler.getListenerManager().addJobListener(afterExecListener, KeyMatcher.keyEquals(jobKey));
        }
        if (block) {//如果要阻塞等待回调结果
            long start = System.currentTimeMillis();
            int state = afterExecListener.getState();
            while (state != 1 && (System.currentTimeMillis() - start) < 1000L) {
                state = afterExecListener.getState();
            }
        }
    }

    /**
     * 暂停定时任务
     *
     * @param jobName
     * @param jobGroup
     * @throws Exception
     */
    public void pauseSchedule(String jobName, String jobGroup) throws Exception {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢复定时任务
     *
     * @param jobName
     * @param jobGroup
     * @throws Exception
     */
    public void resumeSchedule(String jobName, String jobGroup) throws Exception {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        scheduler.resumeJob(jobKey);
    }

    /**
     * 根据jobName和jobGroup获取jobDataMap
     *
     * @param jobName
     * @param jobGroup
     * @return
     * @throws Exception
     */
    public JobDataMap getJobDataMap(String jobName, String jobGroup) throws Exception {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        JobDataMap jobDataMap = null;
        if (jobDetail != null) {
            jobDataMap = jobDetail.getJobDataMap();
        }
        return jobDataMap;
    }

}

代码解释:
1.在讲解enableSchedule方法之前,我想先讲解一下Scheduler是怎么被注入的。打开QuartzConfig配置类,如下:

@Bean
@ConditionalOnMissingBean(name = "schedulerFactory")
public SchedulerFactoryBean schedulerFactory(DataSource quartzDataSource, JobFactory jobFactory, DataSourceTransactionManager quartzTransactionManager) {
    SchedulerFactoryBean bean = new SchedulerFactoryBean();
    bean.setDataSource(quartzDataSource);
    bean.setTransactionManager(quartzTransactionManager);
    bean.setApplicationContextSchedulerContextKey("applicationContextKey");
//        bean.setConfigLocation(new ClassPathResource("quartz.properties"));
    bean.setJobFactory(jobFactory);
    bean.setTriggers(triggers.toArray(new CronTrigger[]{}));
    return bean;
}

代码解释:

  1. 此处会说明Quartz四大核心概念中的Scheduler。
  2. 看 scheduleFactory方法 ,被@Bean修饰,说明是一个注入的类;@ConditionalOnMissingBean(name = “schedulerFactory”),说明是在缺失schedulerFactory的时候才生效。此方法返回类型是SchedulerFactoryBean,进入到这个类中:
public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>, BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
     // 省略
}

代码解释:

  1. 此处重点在于SchedulerFactoryBean类实现了InitializingBean接口,实现了该接口的类会在应用启动时被注入Spring容器并执行其中的afterPropertiesSet方法,如下:
public void afterPropertiesSet() throws Exception {
    if (this.dataSource == null && this.nonTransactionalDataSource != null) {
        this.dataSource = this.nonTransactionalDataSource;
    }

    if (this.applicationContext != null && this.resourceLoader == null) {
        this.resourceLoader = this.applicationContext;
    }

    this.scheduler = this.prepareScheduler(this.prepareSchedulerFactory());

    try {
        this.registerListeners();
        this.registerJobsAndTriggers();
    } catch (Exception var4) {
        try {
            this.scheduler.shutdown(true);
        } catch (Exception var3) {
            this.logger.debug("Scheduler shutdown exception after registration failure", var3);
        }

        throw var4;
    }
}

代码解释:
此方法的作用就是使用ScheduleFactory(ScheduleFactory在此类中的initSchedulerFactory方法初始化生成,是StdSchedulerFactory类型的)生成Scheduler。

总结:

  1. 配置SchedulerFactoryBean,就会自动为我们生成ScheduleFactory和Scheduler。
    下面回过头来继续看ScheduleJobService中的enableSchedule方法:
/**
 * 启用定时任务或重设定时任务的触发时间
 *
 * @param job
 * @param jobDataMap
 * @throws Exception
 */
public void enableSchedule(ScheduleJob job, JobDataMap jobDataMap) throws Exception {
    if (job == null) {
        return;
    }
    JobDetail jobDetail = JobBuilder.newJob(job.getJobExecuteClass())
            .withIdentity(job.getJobName(), job.getJobGroup().getCode())
            .withDescription(job.getJobGroup().getDesc())
            .build();
    if (jobDataMap != null) {
        jobDetail.getJobDataMap().putAll(jobDataMap);
    }
    //表达式调度构建器
    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
    //按新的cronExpression表达式构建一个新的trigger
    Trigger trigger = TriggerBuilder.newTrigger()
            .withIdentity(job.getTriggerName(), job.getJobGroup().getCode())
            .withSchedule(scheduleBuilder)
            .withDescription(job.getJobGroup().getDesc())
            .build();
    Trigger exists = scheduler.getTrigger(trigger.getKey());
    if (exists != null) {
        if (exists instanceof CronTriggerImpl) {
            ((CronTriggerImpl) trigger).setPreviousFireTime(exists.getPreviousFireTime());
        } else if (exists instanceof CalendarIntervalTriggerImpl) {
            ((CalendarIntervalTriggerImpl) trigger).setPreviousFireTime(exists.getPreviousFireTime());
        } else if (exists instanceof DailyTimeIntervalTriggerImpl) {
            ((DailyTimeIntervalTriggerImpl) trigger).setPreviousFireTime(exists.getPreviousFireTime());
        } else if (exists instanceof SimpleTriggerImpl) {
            ((SimpleTriggerImpl) trigger).setPreviousFireTime(exists.getPreviousFireTime());
        }
    }
    scheduler.scheduleJob(jobDetail, Sets.newHashSet(trigger), true);
}

代码解释:
Quartz四大概念中的最后二位登场了,它就是JobDetail和Trigger,在这个方法中,将设置JobDetail程序和Trigger触发器并且将其放入Scheduler容器执行。

5) SimpleAbstractJob:
package com.wyc.demo.quartz.support;

import com.wyc.demo.entity.ScheduleJob;
import org.quartz.JobDataMap;


/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:41
 * @description:
 */
public abstract class SimpleAbstractJob extends CommonAbstractJob {

    /**
     * 获取执行表达式
     * @return
     */
    public abstract String getCronExpression();

    /**
     * 获取JobDataMap
     * @return
     */
    public abstract JobDataMap getJobDataMap();

    /**
     * 获取JobName
     * @return
     */
    public abstract String getJobName();

    public void startJob() throws Exception {
        String cronExpression = getCronExpression();
        //jobName不要包含时间戳,和group不能同时重复
        ScheduleJob job = new ScheduleJob(getJobName(), getJobGroup(), cronExpression, getClass());
        enableSchedule(job, getJobDataMap());
        logger.info("---设置完成:{}---", cronExpression);
    }

    /**
     * 停止定时器 
     *
     * @throws Exception
     */
    public void stopJob() throws Exception {
        scheduleJobService.removeSchedule(getJobName(), getJobGroupString());
    }

    /**
     * 立即运行定时器 
     *
     * @param delete
     * @param block
     * @throws Exception
     */
    public void runJob(boolean delete, boolean block) throws Exception {
        scheduleJobService.execSchedule(getJobName(), getJobGroupString(), getJobDataMap(), delete, block);
    }

}

代码解释:

  1. 继承CommonAbstractJob抽象类,此类的重点在于 startJob()方法,这是定时任务的启动方法!ScheduleJob类是我们自己定义的包装了jobName、jobGroup、cronExpression等信息的实体类。如下:
package com.wyc.demo.entity;


import com.wyc.demo.enums.JobGroupType;
import org.quartz.Job;

import java.io.Serializable;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:25
 * @description:
 */
public class ScheduleJob implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = -3454363184589312090L;
    private String jobName;
    private JobGroupType jobGroup;
    private Integer jobStatus;
    private String cronExpression;
    private String desc;
    private Class<? extends Job> jobExecuteClass;

    public ScheduleJob() {
        super();
    }

    public ScheduleJob(String jobName, JobGroupType jobGroup) {
        super();
        this.jobName = jobName;
        this.jobGroup = jobGroup;
    }

    public ScheduleJob(String jobName, JobGroupType jobGroup, String cronExpression) {
        super();
        this.jobName = jobName;
        this.jobGroup = jobGroup;
        this.cronExpression = cronExpression;
    }

    public ScheduleJob(String jobName, JobGroupType jobGroup, String cronExpression,
                       Class<? extends Job> jobExecuteClass) {
        super();
        this.jobName = jobName;
        this.jobGroup = jobGroup;
        this.cronExpression = cronExpression;
        this.jobExecuteClass = jobExecuteClass;
    }

    public String getTriggerName() {
        return this.getJobName();
    }

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }

    public JobGroupType getJobGroup() {
        return jobGroup;
    }

    public void setJobGroup(JobGroupType jobGroup) {
        this.jobGroup = jobGroup;
    }

    public Integer getJobStatus() {
        return jobStatus;
    }

    public void setJobStatus(Integer jobStatus) {
        this.jobStatus = jobStatus;
    }

    public String getCronExpression() {
        return cronExpression;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public Class<? extends Job> getJobExecuteClass() {
        return jobExecuteClass;
    }

    public void setJobExecuteClass(Class<? extends Job> jobExecuteClass) {
        this.jobExecuteClass = jobExecuteClass;
    }
}
  1. enableSchedule方法就是执行ScheduleJobService中的enableSchedule方法。
JobGroupType
package com.wyc.demo.enums;
/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:25
 * @description:
 */
public enum JobGroupType {
    DEMO_JOB("DEMO_JOB", "示例定时任务"),
    TEST_JOB("TEST_JOB", "测试定时任务");

    private String code;
    private String desc;

    private JobGroupType(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code == null ? null : code.trim();
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc == null ? null : desc.trim();
    }
}
6) DemoJob:
package com.wyc.demo.quartz.job;

import com.wyc.demo.enums.JobGroupType;
import com.wyc.demo.quartz.support.SimpleAbstractJob;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:41
 * @description:
 */
@Component
public class DemoJob extends SimpleAbstractJob {

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

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private String cronExpression = "0 0/1 * * * ?";

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Date nowTime = new Date();
        logger.info("Demo-定时器===>执行Demo-定时任务开始,当前时间:{}", DATE_FORMAT.format(nowTime));
    }

    @Override
    public String getCronExpression() {
        return cronExpression;
    }

    @Override
    public JobDataMap getJobDataMap() {
        return null;
    }

    @Override
    public String getJobName() {
        return getClass().getSimpleName();
    }

    @Override
    public JobGroupType getJobGroup() {
        return JobGroupType.DEMO_JOB;
    }

}

代码解释:

  1. DemoJob是我们自己的Job类,此类继承SimpleAbstractJob抽象类(继承了startJob方法),间接实现了Job接口的execute方法(我们定时器的业务代码写在这里面),也就是说我们的DemoJob类既拥有了startJob方法也拥有了execute方法。
  2. 由上述代码可见,我们定时器任务每隔1分钟打印一次日志。
package com.wyc.demo.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author: wangyuanchen
 * @date: 2020-10-27 14:44
 * @description:
 */
@Component
public class SpringBeanUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringBeanUtils.applicationContext=applicationContext;
    }

    public static <T> T getBean(Class<T> clz){
        return applicationContext.getBean(clz);
    }
}
package com.wyc.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author: wangyuanchen
 * @date: 2020-10-30 08:56
 * @description:
 */
@SpringBootApplication
public class DemoApplication {

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

}

总结:

源码地址:github_quartz_demo

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。 Quartz的优势: 1、Quartz是一个任务调度框架(库),它几乎可以集成到任何应用系统中。 2、Quartz是非常灵活的,它让您能够以最“自然”的方式来编写您的项目的代码,实现您所期望的行为 3、Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳出框架来使用,如果你的需求是一些相对基本的简单的需求的话。 4、Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失。 5、可以通过Quartz,封装成自己的分布式任务调度,实现强大的功能,成为自己的产品。6、有很多的互联网公司也都在使用Quartz。比如美团 Spring是一个很优秀的框架,它无缝的集成了Quartz,简单方便的让企业级应用更好的使用Quartz进行任务的调度。   课程说明:在我们的日常开发中,各种大型系统的开发少不了任务调度,简单的单机任务调度已经满足不了我们的系统需求,复杂的任务会让程序猿头疼, 所以急需一套专门的框架帮助我们去管理定时任务,并且可以在多台机器去执行我们的任务,还要可以管理我们的分布式定时任务。本课程从Quartz框架讲起,由浅到深,从使用到结构分析,再到源码分析,深入解析Quartz、Spring+Quartz,并且会讲解相关原理, 让大家充分的理解这个框架框架的设计思想。由于互联网的复杂性,为了满足我们特定的需求,需要对Spring+Quartz进行二次开发,整个二次开发过程都会进行讲解。Spring被用在了越来越多的项目中, Quartz也被公认为是比较好用的定时器设置工具,学完这个课程后,不仅仅可以熟练掌握分布式定时任务,还可以深入理解大型框架的设计思想。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值