本文讲述在基于SpringBoot框架的项目中,如何一步一步的集成Quartz框架,项目使用的是PostgreSQL数据库。
<!-- quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
在resource目录下创建servicex-quartz.properties文件:
#============================================================================
# 1. 基本配置
#============================================================================
# 调度标识名, 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName = SERVICEX-SCHEDULER-INSTANCE-NAME
# ID设置为自动获取, 每一个实例不能相同
org.quartz.scheduler.instanceId = AUTO
#============================================================================
# 2. 调度器线程池配置
#============================================================================
# 线程池的实现类, 一般使用SimpleThreadPool即可满足需求
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 指定线程数无默认值, 至少为1
org.quartz.threadPool.threadCount = 10
# 设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority = 5
#============================================================================
# 3. 作业存储配置
#============================================================================
# 数据保存方式为数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 数据库驱动
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
# 是否加入集群
org.quartz.jobStore.isClustered = true
# 检查集群节点状态的频率, 默认值是 15000(即15秒)
org.quartz.jobStore.clusterCheckinInterval = 15000
org.quartz.jobStore.maxMisfiresToHandleAtATime = 1
org.quartz.jobStore.txIsolationLevelSerializable = true
# 设置调度引擎对触发器超时的忍耐时间 (单位毫秒)
org.quartz.jobStore.misfireThreshold = 12000
# 表的前缀,默认QRTZ_
org.quartz.jobStore.tablePrefix = QRTZ_
在官网中找到对于的SQL脚本:
-- Thanks to Patrick Lightbody for submitting this...
--
-- In your Quartz properties file, you'll need to set
-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
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_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_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 BOOL NOT NULL,
IS_NONCONCURRENT BOOL NOT NULL,
IS_UPDATE_DATA BOOL NOT NULL,
REQUESTS_RECOVERY BOOL NOT NULL,
JOB_DATA BYTEA 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 NULL,
PREV_FIRE_TIME BIGINT NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT NOT NULL,
END_TIME BIGINT NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT NULL,
JOB_DATA BYTEA 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 NOT NULL,
REPEAT_INTERVAL BIGINT NOT NULL,
TIMES_TRIGGERED BIGINT 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(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)
);
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 BOOL NULL,
BOOL_PROP_2 BOOL 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 BYTEA 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 BYTEA 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 NOT NULL,
SCHED_TIME BIGINT NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT BOOL NULL,
REQUESTS_RECOVERY BOOL 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 NOT NULL,
CHECKIN_INTERVAL BIGINT 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)
);
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;
@Configuration
public class SchedulerConfig {
// 配置文件路径
private static final String SERVICEX_QUARTZ_CONFIG_PATH = "/servicex-quartz.properties";
// 控制器工厂类
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
factory.setQuartzProperties(quartzProperties());
factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("SERVICEX-APPLICATION-CONTEXT-KEY");
factory.setOverwriteExistingJobs(true);
factory.setAutoStartup(true);
return factory;
}
@Bean
@ConditionalOnResource(resources = SERVICEX_QUARTZ_CONFIG_PATH)
Properties quartzProperties() {
try {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource(SERVICEX_QUARTZ_CONFIG_PATH));
propertiesFactoryBean.afterPropertiesSet();
Properties properties = propertiesFactoryBean.getObject();
return properties;
} catch (IOException e) {
System.out.println("QUARTZ相关的配置文件不存在.");
e.printStackTrace();
}
return null;
}
}
/**
* 抽象JOB的封装
* JOB表示一个任务, 也就是执行的具体的内容, 一个JOB可以被多个TRIGGER关联, 但是一个TRIGGER只能关联一个JOB.
* @author ROCKY
* @createTime 2022年07月04日
*/
@Slf4j
public abstract class AbstractQuartzJob implements Job {
// 线程本地变量
private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
ServicexJob job = new ServicexJob();
BeanUtils.copyProperties(context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES), job);
try {
before(context, job);
if (job != null) {
doExecute(context, job);
}
after(context, job, null);
} catch (Exception e) {
after(context, job, e);
log.error("任务执行异常:", e);
}
}
// 1. 执行前
protected void before(JobExecutionContext context, ServicexJob job) {
threadLocal.set(new Date());
}
// 2. 执行方法, 实际的子JOB重载该方法
protected abstract void doExecute(JobExecutionContext context, ServicexJob job) throws Exception;
// 3. 执行后
protected void after(JobExecutionContext context, ServicexJob job, Exception e) {
threadLocal.remove();
// 质检完成后会做一些操作
job.doSomeThings();
}
}
/**
* 不支持并发执行任务的JOB(禁止并发执行, 表示Quartz不能并发的执行同一个JOB的定义, 特指一个JOB类的多个实例)
* @author ROCKY
* @createTime 2022年07月04日
*/
@Slf4j
@DisallowConcurrentExecution
public class MyJobExecution extends AbstractQuartzJob {
@Override
protected void doExecute(JobExecutionContext context, ServicexJob job) throws Exception {
// 真正的调用逻辑
log.info("TO DO SOMETHING."+ job.getCronExpression() + "[" + job.getNextValidTime() + "]");
}
}
/**
* 调度器服务
* JobDetail:用于定义作业的实例
* Trigger: 触发器, 执行给定作业计划的组件实例
* Scheduler: 与调度程序交互的主要的API
* CronScheduleBuilder:用于创建一个Scheduler的生成器
* 何时触发, 通过Trigger来定义, 使用TriggerBuilder进行构建
* 什么任务, 通过JobDetail来定义, 使用JobBuilder进行构建
* 执行逻辑, 通过JOB中的doExecute方法的具体实现, 执行具体的内容
*
* @author ROCKY
* @createTime 2022年07月04日
*/
public class ServicexScheduler {
// 1. 创建/新增任务
public static void register(Scheduler scheduler, ServicexJob job, Class<? extends Job> jobClass) throws SchedulerException {
// 1. 构建任务信息
String jobId = job.getJobId();
String jobGroup = job.getJobGroup();
// 2.1 构建JOB-DETAIL
JobKey jobKey = getJobKey(jobId, jobGroup);
TriggerKey triggerKey = getTriggerKey(jobId, jobGroup);
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobKey).build();
// 2.2 设置参数
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
// 3. 构建CRON调度器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
try {
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
} catch (Exception e) {
e.printStackTrace();
}
// 4. 构建触发器
// .startAt(), 用于指定开始时间
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
.startAt(job.getStartTime())
.withSchedule(cronScheduleBuilder).build();
// 5. 判断是否存在该定时任务
if (scheduler.checkExists(jobKey)) {
// 防止创建时存在数据问题(先移除再创建)
scheduler.deleteJob(jobKey);
}
// 6. 根据JOB-DETAIL和建触发器创建定时任务
scheduler.scheduleJob(jobDetail, trigger);
// 7. 如果JOB的状态是暂停的/禁用的, 需要暂定该任务
if (!job.getStatus().equals(ScheduleConstants.Status.ENABLED.getValue())) {
scheduler.pauseJob(jobKey);
}
}
public static void register(Scheduler scheduler, ServicexJob job) throws SchedulerException {
register(scheduler, job, MyJobExecution.class);
}
// 2. 暂停任务
public static int pause(Scheduler scheduler, ServicexJob job) throws SchedulerException {
String jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobKey jobKey = getJobKey(jobId, jobGroup);
if (StringUtils.isNotEmpty(jobId) && StringUtils.isNotEmpty(jobGroup) && scheduler.checkExists(jobKey)) {
scheduler.pauseJob(jobKey);
return 1;
}
return -1;
}
// 3. 恢复任务
public static int resume(Scheduler scheduler, ServicexJob job) throws SchedulerException {
String jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobKey jobKey = getJobKey(jobId, jobGroup);
if (StringUtils.isNotEmpty(jobId) && StringUtils.isNotEmpty(jobGroup) && scheduler.checkExists(jobKey)) {
scheduler.resumeJob(jobKey);
return 1;
}
return -1;
}
// 4. 删除任务
public static int delete(Scheduler scheduler, ServicexJob job) throws SchedulerException {
String jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobKey jobKey = getJobKey(jobId, jobGroup);
if (StringUtils.isNotEmpty(jobId) && StringUtils.isNotEmpty(jobGroup) && scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
return 1;
}
return -1;
}
// 5. 运行任务
public static void run(Scheduler scheduler, ServicexJob job) throws SchedulerException {
String jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobDataMap dataMap = new JobDataMap();
dataMap.put(ScheduleConstants.TASK_PROPERTIES, job);
scheduler.triggerJob(getJobKey(jobId, jobGroup), dataMap);
}
// 6. 更新任务
public static void update(Scheduler scheduler, ServicexJob job) throws SchedulerException {
String jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobKey jobKey = getJobKey(jobId, jobGroup);
if (StringUtils.isNotEmpty(jobId) && StringUtils.isNotEmpty(jobGroup) && scheduler.checkExists(jobKey)) {
// 移除
scheduler.deleteJob(jobKey);
}
// 新建
register(scheduler, job);
}
// 6. 更新任务
public static void update(Scheduler scheduler, ServicexJob job, Class<? extends Job> jobClass) throws SchedulerException {
String jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobKey jobKey = getJobKey(jobId, jobGroup);
if (StringUtils.isNotEmpty(jobId) && StringUtils.isNotEmpty(jobGroup) && scheduler.checkExists(jobKey)) {
// 移除
scheduler.deleteJob(jobKey);
}
// 新建
register(scheduler, job, jobClass);
}
// 获取任务的键
private static JobKey getJobKey(String jobId, String jobGroup) {
return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME_PREFIX + jobId, jobGroup);
}
// 获取任务的键
private static JobKey getJobKey(String taskName, String jobId, String jobGroup) {
String jobName = (StringUtils.isNotEmpty(taskName) ? taskName : ScheduleConstants.TASK_CLASS_NAME_PREFIX) + jobId;
return JobKey.jobKey(jobName, jobGroup);
}
// 构建任务触发器
private static TriggerKey getTriggerKey(String jobId, String jobGroup) {
return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME_PREFIX + jobId, jobGroup);
}
// 设置定时任务执行计划的失火策略
private static CronScheduleBuilder handleCronScheduleMisfirePolicy(ServicexJob job, CronScheduleBuilder cb)
throws Exception {
switch (job.getMisfirePolicy()) {
case ScheduleConstants.MISFIRE_DEFAULT:
return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing();
default:
return null;
}
}
}
在服务启动时注册所有的任务:
@Service
@Slf4j
public class ServicexQuartzServiceImpl implements IServicexQuartzService {
@Autowired
private ServicexQuartzMapperservicexQuartzMapper;
@Autowired
private Scheduler scheduler;
@PostConstruct
public void init() throws SchedulerException {
scheduler.clear();
List<ServicexJob> jobs = new LambdaQueryChainWrapper<>(servicexQuartzMapper).eq(ServicexJob::getRunType, RunType.AUTO.getValue()).list();
for (ServicexJob job : jobs) {
ServicexScheduler.register(scheduler, job);
}
}
...
}
SpringBoot - 集成Quartz框架之CRON表达式
SpringBoot - 集成Quartz框架之Quartz简介(一)
SpringBoot - 集成Quartz框架之常用配置(二)
SpringBoot - 集成Quartz框架之具体步骤(三)
SpringBoot - 集成Quartz框架之独立数据源(四)
SpringBoot - 集成Quartz框架之常见问题(五)
SpringBoot - 集成Quartz框架之@DisallowConcurrentExecution注解详解(六)