一、背景介绍
项目早期为单体应用,近期因业务量上涨,架构逐渐转为springcloud+分布式集群。原先项目中的定时任务主要采用@Scheduled注解方式实现,并且因历史原因,分布散乱,管理不便。@Scheduled注解的定时任务无法直接应用于集群环境,并且服务重启或异常时,任务容易丢失。Quartz支持对数据的持久化,并且有misfire机制,任务不易丢失,同时支持集群设置,分离出的定时任务可独立为微服务应用,扩展性好。
二、代码实现
- 添加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。
四、参考资料
- quartz API http://www.quartz-scheduler.org/api/2.2.1/index.html
- spring API https://docs.spring.io/spring/docs/4.3.0.BUILD-SNAPSHOT/javadoc-api/
- 简单 Quartz-Cluster 微服务
- quartz (从原理到应用)详解篇