一、背景介绍

    项目早期为单体应用,近期因业务量上涨,架构逐渐转为springcloud+分布式集群。原先项目中的定时任务主要采用@Scheduled注解方式实现,并且因历史原因,分布散乱,管理不便。@Scheduled注解的定时任务无法直接应用于集群环境,并且服务重启或异常时,任务容易丢失。Quartz支持对数据的持久化,并且有misfire机制,任务不易丢失,同时支持集群设置,分离出的定时任务可独立为微服务应用,扩展性好。

二、代码实现

  1. 添加maven依赖

<?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.cpdiem.demo</groupId>
   <artifactId>quick-quartz</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>quick-quartz</name>
   <description>Demo project for Spring Boot</description>

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

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

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

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

      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.12</version>
         <scope>test</scope>
      </dependency>

      <!-- 访问数据库模块 -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>

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

      <!-- MYSQL -->
      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
      </dependency>

      <!-- Jdbc 模块 -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jdbc</artifactId>
      </dependency>

      <!-- quartz 模块 -->
      <dependency>
         <groupId>org.quartz-scheduler</groupId>
         <artifactId>quartz</artifactId>
         <version>2.3.0</version>
      </dependency>
      <dependency>
         <groupId>org.quartz-scheduler</groupId>
         <artifactId>quartz-jobs</artifactId>
         <version>2.3.0</version>
      </dependency>
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context-support</artifactId>
      </dependency>

      <!-- druid 线程池模块 -->
      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid</artifactId>
         <version>1.1.3</version>
      </dependency>

      <!-- hikari 连接池-->
      <dependency>
         <groupId>hikari-cp</groupId>
         <artifactId>hikari-cp</artifactId>
         <version>2.6.0</version>
      </dependency>

      <!-- redis缓存 -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
         <version>2.1.1.RELEASE</version>
      </dependency>

      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.47</version>
      </dependency>

   </dependencies>

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


</project>
1. 执行Quartz相关表入数据库(quick-quartz\quartz-tables.log) 
  
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)
);

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)
);

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)
);

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(200) 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)
);

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)
);

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),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

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)
);

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

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)
);

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)
);

CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);


commit;
1. 添加应用配置(quick-quartz\src\main\resources\application.yml) 
  
server:
  port: 8088
spring:
  application:
    name: quick-quartz
    datasource:
#      type: com.zaxxer.hikari.HikariDataSource
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&userSSL=false&serverTimezone=GMT
      username: root
      password: root
      hikari:
        #
        minimum-idle: 5
        #
        idle-timeout: 180000
        #
        maximum-pool-size: 10
        aoto-commit: true
        pool-name: MyHikariCP
        connection-timeout: 30000


    jpa:
      hibernate:
        #ddl-auto: create 
        ddl-auto: update 
      show-sql: true

  redis:
    database: 0
    host: localhost
    port: 6379
    password:
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0
    timeout: 5000
1. 添加Quartz配置文件(quick-quartz\src\main\resources\config\quartz.properties)                                                                 配置说明可参考官方文档 http://www.quartz-scheduler.org/documentation/quartz-2.2.x/configuration/                                   w3cschool亦有中文版翻译 https://www.w3cschool.cn/quartz_doc/quartz_doc-i7oc2d9l.html 
  
org.quartz.scheduler.instanceName = quartzScheduler  
org.quartz.scheduler.instanceId = AUTO  


org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX  
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_  
org.quartz.jobStore.isClustered = true  
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.clusterCheckinInterval = 20000    


org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool  
org.quartz.threadPool.threadCount = 10  
org.quartz.threadPool.threadPriority = 5  
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
1. 添加触发器配置文件(quick-quartz\src\main\resources\config\timers.properties) 
  
quartz.timer1=0/15 * * * * ?

quartz.timer2=0 0/2 * * * ?

quartz.timer3=0/30 * * * * ?
1. 配置文件工具类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\utils\PropertiesUtils.java) 
  
package com.cpdiem.demo.quickquartz.utils;

import com.alibaba.fastjson.util.TypeUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class PropertiesUtils extends PropertyPlaceholderConfigurer {

    private static Map<String, Object> ctxPropertiesMap;

    /**
     * 重写processProperties方法
     */
    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
            throws BeansException {
        super.processProperties(beanFactoryToProcess, props);
        ctxPropertiesMap = new HashMap<String, Object>();
        for (Object key : props.keySet()) {
            String keyStr = key.toString();
            String value = props.getProperty(keyStr);
            ctxPropertiesMap.put(keyStr, value);
        }
    }

    public static String getProperty(String key) {
        return TypeUtils.castToString(ctxPropertiesMap.get(key));
    }

    public static Object getObject(String key, Class clazz) {
        Object obj = getProperty(key);
        return TypeUtils.castToJavaBean(obj, clazz);
    }

    public static Boolean getBoolean(String key) {
        Object value = getProperty(key);
        if (value == null)
            return null;
        else
            return TypeUtils.castToBoolean(value);
    }

    public static byte[] getBytes(String key) {
        Object value = getProperty(key);
        if (value == null)
            return null;
        else
            return TypeUtils.castToBytes(value);
    }

    public static boolean getBooleanValue(String key) {
        Object value = getProperty(key);
        if (value == null)
            return false;
        else
            return TypeUtils.castToBoolean(value).booleanValue();
    }

    public static Byte getByte(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToByte(value);
    }

    public static byte getByteValue(String key) {
        Object value = getProperty(key);
        if (value == null)
            return 0;
        else
            return TypeUtils.castToByte(value).byteValue();
    }

    public static Short getShort(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToShort(value);
    }

    public static short getShortValue(String key) {
        Object value = getProperty(key);
        if (value == null)
            return 0;
        else
            return TypeUtils.castToShort(value).shortValue();
    }

    public static Integer getInteger(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToInt(value);
    }

    public static int getIntValue(String key) {
        Object value = getProperty(key);
        if (value == null)
            return 0;
        else
            return TypeUtils.castToInt(value).intValue();
    }

    public static Long getLong(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToLong(value);
    }

    public static long getLongValue(String key) {
        Object value = getProperty(key);
        if (value == null)
            return 0L;
        else
            return TypeUtils.castToLong(value).longValue();
    }

    public static Float getFloat(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToFloat(value);
    }

    public static float getFloatValue(String key) {
        Object value = getProperty(key);
        if (value == null)
            return 0.0F;
        else
            return TypeUtils.castToFloat(value).floatValue();
    }

    public static Double getDouble(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToDouble(value);
    }

    public static double getDoubleValue(String key) {
        Object value = getProperty(key);
        if (value == null)
            return 0.0D;
        else
            return TypeUtils.castToDouble(value).doubleValue();
    }

    public static BigDecimal getBigDecimal(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToBigDecimal(value);
    }

    public static BigInteger getBigInteger(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToBigInteger(value);
    }

    public static String getString(String key) {
        Object value = getProperty(key);
        if (value == null)
            return null;
        else
            return value.toString();
    }

    public static Date getDate(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToDate(value);
    }

    public static java.sql.Date getSqlDate(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToSqlDate(value);
    }

    public static Timestamp getTimestamp(String key) {
        Object value = getProperty(key);
        return TypeUtils.castToTimestamp(value);
    }


}
1. redis工具类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\utils\RedisUtils.java) 
  
package com.cpdiem.demo.quickquartz.utils;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Set;

@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key){
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object...values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * @param key 键
     * @return 移除的个数
     */
    public long setRemove(String key,Object... objects) {
        try {
            Long count = redisTemplate.opsForSet().remove(key,objects);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


}
1. 任务实现类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\Jobs\Job1.java                                                     quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\Jobs\Job2.java) 
  
package com.cpdiem.demo.quickquartz.Jobs;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

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

/**
 * 定时调度任务Job1.
 *
 *
 */
@Component
public class Job1 extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
        System.out.println("@@@@@@@@@@@@        "+(new SimpleDateFormat("yyyy-mm-dd HH:mm:ss.SSSSSS")).format(new Date())+"         @@@@@@@@@@@");
        System.out.println("@@@@@@@@@@@@@@@@@@@@@@@        Job1         @@@@@@@@@@@@@@@@@@@@@@");
        System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
        System.out.println();
    }
}package com.cpdiem.demo.quickquartz.Jobs;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

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

/**
 * 定时调度任务Job2.
 *
 */
public class Job2 extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("==================================================================");
        System.out.println("===========        "+(new SimpleDateFormat("yyyy-mm-dd HH:mm:ss.SSSSSS")).format(new Date())+"         ============");
        System.out.println("=======================        Job2         ======================");
        System.out.println("==================================================================");
        System.out.println();
    }
}
1.  
2. redis配置类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\config\RedisConfig.java) 
  
package com.cpdiem.demo.quickquartz.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

public class RedisConfig {


    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();

        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();

        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(om);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式

        template.setKeySerializer(stringRedisSerializer);

        // hash的key也采用String的序列化方式

        template.setHashKeySerializer(stringRedisSerializer);

        // value序列化方式采用jackson

        template.setValueSerializer(jackson2JsonRedisSerializer);

        // hash的value序列化方式采用jackson

        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();

        return template;

    }
}
1. 任务调度类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\config\QuartzSchedulerConfig.java) 
  
package com.cpdiem.demo.quickquartz.config;


import com.cpdiem.demo.quickquartz.Jobs.Job1;
import com.cpdiem.demo.quickquartz.Jobs.Job2;
import com.cpdiem.demo.quickquartz.utils.PropertiesUtils;
import com.cpdiem.demo.quickquartz.utils.RedisUtils;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;


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

/**
 * Quartz调度配置类。
 *
 *
 */
@Configuration
public class QuartzSchedulerConfig {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private RedisUtils redisUtils;

    private static final Logger Logger = LoggerFactory.getLogger(QuartzSchedulerConfig.class);
    private static final String QUARTZ_PROPERTIES_NAME = "/config/quartz.properties";

    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext) {
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, Trigger[] triggers, JobDetail[]
            jobDetails) {
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        try {
            factoryBean.setQuartzProperties(quartzProperties());
            factoryBean.setDataSource(dataSource);
            factoryBean.setJobFactory(jobFactory);
            factoryBean.setTriggers(triggers);
            factoryBean.setJobDetails(jobDetails);
            factoryBean.setOverwriteExistingJobs(true);
            factoryBean.setStartupDelay(10);
            factoryBean.setWaitForJobsToCompleteOnShutdown(true);
        } catch (Exception e) {
            Logger.error("加载 {} 配置文件失败.", QUARTZ_PROPERTIES_NAME, e);
            throw new RuntimeException("加载配置文件失败", e);
        }
        tempSetTriggers(triggers);
        return factoryBean;
    }

    void tempSetTriggers(Trigger[] triggers){
        if(triggers.length>0){
            for(Trigger trigger : triggers){
                redisUtils.sSet("Triggers",trigger.getKey().getName());
            }
        }
    }


    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource(QUARTZ_PROPERTIES_NAME));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

        private transient AutowireCapableBeanFactory beanFactory;

        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }

        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle)
                throws Exception {
            final Object job = super.createJobInstance(bundle);
            beanFactory.autowireBean(job);
            return job;
        }
    }

    @Bean(name = "job1Trigger")
    public CronTriggerFactoryBean job1Trigger(@Qualifier("job1Detail") JobDetail jobDetail) {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
        cronTriggerFactoryBean.setJobDetail(jobDetail);
        cronTriggerFactoryBean.setCronExpression(PropertiesUtils.getProperty("quartz.timer1"));
        cronTriggerFactoryBean.setGroup("first");
        cronTriggerFactoryBean.setName("job1Trigger");
        return cronTriggerFactoryBean;
    }

    @Bean(name = "job1Detail")
    public JobDetailFactoryBean job1Detail() {
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(Job1.class);
        jobDetailFactoryBean.setDurability(true);
        return jobDetailFactoryBean;
    }

    @Bean(name = "job2Trigger")
    public CronTriggerFactoryBean job2Trigger(@Qualifier("job2Detail") JobDetail jobDetail) {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
        cronTriggerFactoryBean.setJobDetail(jobDetail);
        cronTriggerFactoryBean.setCronExpression(PropertiesUtils.getProperty("quartz.timer2"));
        return cronTriggerFactoryBean;
    }

    @Bean(name = "job2Detail")
    public JobDetailFactoryBean job2Detail() {
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(Job2.class);
        jobDetailFactoryBean.setDurability(true);
        return jobDetailFactoryBean;
    }

//    @Bean(name = "job3Trigger")
//    public CronTriggerFactoryBean job3Trigger(@Qualifier("job1Detail") JobDetail jobDetail) {
//        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
//        cronTriggerFactoryBean.setJobDetail(jobDetail);
//        cronTriggerFactoryBean.setCronExpression(PropertiesUtils.getProperty("quartz.timer3"));
//        return cronTriggerFactoryBean;
//    }


}
任务辅助类(quick-quartz\src\main\java\com\cpdiem\demo\quickquartz\service\JobTaskService.java)
package com.cpdiem.demo.quickquartz.service;

import com.cpdiem.demo.quickquartz.utils.RedisUtils;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Component
public class JobTaskService {

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    @Autowired
    private RedisUtils redisUtils;

    @PostConstruct
    private void updateJobTaskBeforeStarting(){

        Set<String> hashSet = new HashSet<String>();
        Set set = redisUtils.sGet("Triggers");
        if(set!=null && set.size()>0){
            for(Object obj : set){
                hashSet.add((String)obj);
            }
        }

        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        Set<JobKey> jobKeySet = null;
        try {
            jobKeySet = scheduler.getJobKeys(GroupMatcher.<JobKey>anyGroup());
            if(jobKeySet != null && jobKeySet.size()>0) {
                for (JobKey jk : jobKeySet) {
                    List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jk);
                    if (triggers != null && triggers.size() > 0) {
                        for (Trigger trigger : triggers) {
                            if(!hashSet.contains(trigger.getKey().getName())){
                                scheduler.unscheduleJob(trigger.getKey());
                            }

                        }
                    }
                }
            }
            redisUtils.setRemove("Triggers",hashSet.toArray());
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

    }
}
启动类
@SpringBootApplication
public class QuickQuartzApplication {

   public static void main(String[] args) {
      ConfigurableApplicationContext configurableApplicationContext =SpringApplication.run(QuickQuartzApplication.class, args);
      configurableApplicationContext.getEnvironment();
   }

   @Bean
   public PropertiesUtils PropertiesUtils(){
      PropertiesUtils propertiesUtils = new PropertiesUtils();
      propertiesUtils.setLocation(new ClassPathResource("/config/timers.properties"));
      return propertiesUtils;
   }
}

三、简要说明

factoryBean.setOverwriteExistingJobs(true);

该参数默认值为false。当应用启动时,quartz不会更新相关数据库表中的数据(如cron表达式,任务下次执行时间等等),调度器将根据数据库原有数据信息执行任务。

该参数设为true时,数据库数据会被重新设置。如果应用重启、宕机期间有任务错过,将不触发misfire。

quartz在创建完scheduler时,会同时维护数据库及本次应用创建的任务,也就是说quartz只会进行新增、更新操作,不会执行删除操作。因此,对于不再使用的触发器,需自行手动删除(任务辅助类JobTaskService存在的意义,当然它可以做得更多)。

factoryBean.setStartupDelay(10);

设置调度中心在应用启动后n秒后开始运行。

factoryBean.setWaitForJobsToCompleteOnShutdown(true);

该参数用于设置当服务正常关闭时,会等所有任务执行完毕后再行关闭。但仍存在不正常关闭的bug。

 

四、参考资料

  1. quartz API  http://www.quartz-scheduler.org/api/2.2.1/index.html
  2. spring API  https://docs.spring.io/spring/docs/4.3.0.BUILD-SNAPSHOT/javadoc-api/
  3. 简单 Quartz-Cluster 微服务  
  4. quartz (从原理到应用)详解篇