一、前言
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:
1.持久性作业 - 就是保持调度定时的状态
2.作业管理 - 对调度作业进行有效的管理
二、引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
三、Task或Job实现
首先我们需要定义实现一个定时功能的接口,我们可以称之为Task(或Job),如定时发送邮件的task(Job),重启机器的task(Job),优惠券到期发送短信提醒的task(Job),在springboot中只需要继承QuartzJobBean并重写executeInternal方法。
java
public class TestQuartz extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date()) + "\t\tquartz task app is running...... ");
}
}
四、添加配置类
java
@Configuration
public class QuartzConfig {
@Bean
public JobDetail teatQuartzDetail() {
return JobBuilder.newJob(TestQuartz.class).storeDurably().build();
}
@Bean
public Trigger testQuartzTrigger() {
SimpleScheduleBuilder scheduleBuilder =
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(10) // 每隔两秒实行一次
.repeatForever();
return TriggerBuilder.newTrigger().startAt(new Date())
.forJob(teatQuartzDetail())
.withSchedule(scheduleBuilder).build();
}
@Bean
public Trigger testQuartzTrigger2() {
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(teatQuartzDetail())
.withIdentity("myTrigger1","myTriggerGroup1")
.usingJobData("job_trigger_param","job_trigger_param1")
.startNow()
//.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
.withSchedule(CronScheduleBuilder.cronSchedule("0/8 * * * * ? 2020"))
.build()
return trigger;
}
}
五、测试
运行项目,日志打印如下:
2020-10-19 18:27:24 quartz task app is running......
2020-10-19 18:27:28 quartz task app is running......
2020-10-19 18:27:32 quartz task app is running......
2020-10-19 18:27:38 quartz task app is running......
2020-10-19 18:27:40 quartz task app is running......
2020-10-19 18:27:48 quartz task app is running......
2020-10-19 18:27:48 quartz task app is running......
我们发现执行频率一个是8秒一次,一个是10秒一次
六、quartz持久化
6.1 背景
最近在做项目,项目中有个需求:需要使用定时任务,这个定时任务需要即时生效。
Quartz提供两种基本作业存储类型:
1.RAMJobStore :RAM也就是内存,默认情况下Quartz会将任务调度存在内存中,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失
2.JDBC作业存储:存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢。
只有持久化以后,这样定时任务就可以做集群,任务可以进行管理,随时停止、暂停、修改任务。
6.2 Quartz初始化
6.2.1 脚本下载
从官网下载的quartz文件。如下所示:

下载后解压,在目录src\org\quartz\impl\jdbcjobstore中有对应的数据库初始化脚本,这里我们选择mysql数据库,那么需要导入tables_mysql_innodb.sql。
6.2.2 创建数据库
sql
create database quartzdb default character set utf8 collate utf8_general_ci;
create user 'quartz'@'%' identified by 'quartz#123';
create user 'quartz'@'localhost' identified by 'quartz#123';
grant all privileges on quartzdb.* to 'quartz'@'%' identified by 'quartz#123';
grant all privileges on quartzdb.* to 'quartz'@'localhost' identified by 'quartz#123';
flush privileges;
6.2.3 导入数据
导入上面脚本后,具体表信息如下:

表解释如下:
| 表名 | 含义 |
|---|---|
| qrtz_blob_triggers | 以Blob 类型存储的触发器。 |
| qrtz_calendars | 存放日历信息, quartz可配置一个日历来指定一个时间范围。 |
| qrtz_cron_triggers | 存放cron类型的触发器。 |
| qrtz_fired_triggers | 存放已触发的触发器。 |
| qrtz_job_details | 存放一个jobDetail信息。 |
| qrtz_locks | 存储程序的悲观锁的信息(假如使用了悲观锁)。 |
| qrtz_paused_trigger_graps | 存放暂停掉的触发器。 |
| qrtz_scheduler_state | 调度器状态。 |
| qrtz_simple_triggers | 简单触发器的信息。 |
| qrtz_simprop_listeners | 触发器监听器。 |
| qrtz_triggers | 触发器的基本信息。 |
6.3 项目配置
6.3.1 依赖引入
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-quartz</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
6.3.2 application.yml配置
yml
server:
port: 10005
spring:
datasource:
druid:
# 数据库访问配置, 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/quartzdb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&allowMultiQueries=true&useSSL=false
username: quartz
password: quartz#123
# 连接池配置
initial-size: 5
min-idle: 5
max-active: 20
# 连接等待超时时间
max-wait: 30000
# 配置检测可以关闭的空闲连接间隔时间
time-between-eviction-runs-millis: 60000
# 配置连接在池中的最小生存时间
min-evictable-idle-time-millis: 300000
validation-query: select '1' from dual
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-open-prepared-statements: 20
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters, 去掉后监控界面sql无法统计, 'wall'用于防火墙
filters: stat,wall
# Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔
aop-patterns: com.wno704.boot.service.*
# WebStatFilter配置
web-stat-filter:
enabled: true
# 添加过滤规则
url-pattern: /*
# 忽略过滤的格式
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
# StatViewServlet配置
stat-view-servlet:
enabled: true
# 访问路径为/druid时,跳转到StatViewServlet
url-pattern: /druid/*
# 是否能够重置数据
reset-enable: false
# 需要账号密码才能访问控制台
login-username: druid
login-password: druid123
# IP白名单
# allow: 127.0.0.1
# IP黑名单(共同存在时,deny优先于allow)
# deny: 192.168.1.218
# 配置StatFilter
filter:
stat:
log-slow-sql: true
6.3.3 quartz.properties配置
xml
org.quartz.scheduler.instanceName=MyScheduler
#ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的)
org.quartz.scheduler.instanceId=AUTO
#指定调度程序的主线程是否应该是守护线程
org.quartz.scheduler.makeSchedulerThreadDaemon=true
#ThreadPool实现的类名
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#ThreadPool配置线程守护进程
org.quartz.threadPool.makeThreadsDaemons=true
#线程数量
org.quartz.threadPool.threadCount=20
#线程优先级
org.quartz.threadPool.threadPriority=5
#数据保存方式为持久化
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#StdJDBCDelegate说明支持集群
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#quartz内部表的前缀
org.quartz.jobStore.tablePrefix=QRTZ_
#是否加入集群
org.quartz.jobStore.isClustered=true
#容许的最大作业延长时间
org.quartz.jobStore.misfireThreshold=25000
6.3.4 quartz配置类
ScheduleConfig.java
java
@Configuration
public class ScheduleConfig {
@Bean
public ThreadPoolTaskExecutor scheduleJobExecutorService(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(20);
executor.setKeepAliveSeconds(30);
executor.setThreadNamePrefix("fb-Job-Thread");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
factory.setQuartzProperties(quartzProperties());
factory.setSchedulerName("FB_Quartz_Scheduler");
// 延时启动
factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// 启动时更新己存在的 Job
factory.setOverwriteExistingJobs(true);
// 设置自动启动,默认为 true
factory.setAutoStartup(true);
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
}
6.4 任务自定义配置与执行记录
6.4.1 定时任务配置表与记录表
sql
DROP TABLE IF EXISTS QRTZ_JOB;
DROP TABLE IF EXISTS QRTZ_JOB_log;
CREATE TABLE `QRTZ_JOB` (
`JOB_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务id',
`BEAN_NAME` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'spring bean名称',
`METHOD_NAME` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '方法名',
`PARAMS` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '参数',
`CRON_EXPRESSION` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'cron表达式',
`START_TIME` datetime(0) NOT NULL DEFAULT NOW() COMMENT '开始执行时间',
`END_TIME` datetime(0) NOT NULL DEFAULT NOW() COMMENT '结束执行时间',
`STATUS` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务状态 0:正常 1:暂停 2:删除',
`UPDATE_STATUS` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '加载状态 0:未加载 1:已加载',
`REMARK` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
`CREATE_TIME` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`JOB_ID`),
KEY `t_job_create_time` (`CREATE_TIME`)
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '定时任务表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of t_job
-- ----------------------------
INSERT INTO `QRTZ_JOB` VALUES (1, 'loadUpdateTask', 'loadUpdateJob', NULL, '0/30 * * * * ?','2018-03-20 21:55:14', '3000-01-01 00:00:00', '0', '0', '更新 task自动读取', now());
-- ----------------------------
-- Table structure for t_job_log
-- ----------------------------
CREATE TABLE `QRTZ_JOB_LOG` (
`LOG_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志id',
`JOB_ID` bigint(20) NOT NULL COMMENT '任务id',
`BEAN_NAME` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'spring bean名称',
`METHOD_NAME` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '方法名',
`PARAMS` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '参数',
`STATUS` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务状态 0:成功 1:失败',
`ERROR` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '失败信息',
`TIMES` decimal(11, 0) NULL DEFAULT NULL COMMENT '耗时(单位:毫秒)',
`CREATE_TIME` datetime(0) DEFAULT NOW() COMMENT '创建时间',
PRIMARY KEY (`LOG_ID`),
KEY `QRTZ_JOB_LOG_create_time` (`CREATE_TIME`)
) ENGINE = MyISAM AUTO_INCREMENT = 0 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '调度日志表' ROW_FORMAT = Dynamic;
6.4.2 配置表与记录表的实体
Job.java
java
@Data
public class Job implements Serializable {
private static final long serialVersionUID = 21312312424L;
/**
* 任务调度参数 key
*/
public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";
public enum ScheduleStatus {
/**
* 正常
*/
NORMAL("0"),
/**
* 暂停
*/
PAUSE("1"),
/**
* 删除
*/
DEL("2");
private String value;
ScheduleStatus(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
private Long jobId;
private String beanName;
private String methodName;
private String params;
private String cronExpression;
private String startTime;
private String endTime;
private String status;
private String remark;
private String createTime;
}
JobLog.java
java
@Data
public class JobLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务执行成功
*/
public static final String JOB_SUCCESS = "0";
/**
* 任务执行失败
*/
public static final String JOB_FAIL = "1";
private Long logId;
private Long jobId;
private String beanName;
private String methodName;
private String params;
private String status;
private String errorInfo;
private Long times;
private Date createTime;
}
6.4.3 配置表与记录表的实体与实现
JobService.java
java
public interface JobService {
public int init();
/**
* 获取定时任务
*
* @param jobId 任务id
* @return 定时任务
*/
Job findJob(Long jobId);
/**
* 创建定时任务
*
* @param job job
*/
void createJob(Job job);
/**
* 更新定时任务
*
* @param job 定时任务
*/
void updateJob(Job job);
/**
* 删除定时任务
*
* @param jobIds 定时任务id数组
*/
void deleteJobs(String[] jobIds);
/**
* 批量更新定时任务状态
*
* @param jobIds 定时任务id
* @param status 待更新状态
*/
void updateBatch(String jobIds, String status);
/**
* 运行定时任务
*
* @param jobIds 定时任务id
*/
void run(String jobIds);
/**
* 暂停定时任务
*
* @param jobIds 定时任务id
*/
void pause(String jobIds);
/**
* 恢复定时任务
*
* @param jobIds 定时任务id
*/
void resume(String jobIds);
}
JobServiceImpl.java
java
@Service("JobService")
@RequiredArgsConstructor
public class JobServiceImpl implements JobService {
private final Scheduler scheduler;
@Autowired
private JdbcTemplate jdbcTemplateQuartz;
@PostConstruct
@Override
public int init() {
String sql = "SELECT T.JOB_ID,T.BEAN_NAME,T.METHOD_NAME,T.PARAMS,T.CRON_EXPRESSION,T.START_TIME,T.END_TIME,T.STATUS,T.REMARK,T.CREATE_TIME FROM QRTZ_JOB T WHERE T.UPDATE_STATUS = 0 ";
List<Job> scheduleJobList = this.jdbcTemplateQuartz.query(sql, new BeanPropertyRowMapper<Job>(Job.class));
scheduleJobList.forEach(scheduleJob -> {
CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId());
if (cronTrigger == null) {
ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
} else {
ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
}
String upSql = "UPDATE QRTZ_JOB T SET T.UPDATE_STATUS = '1' WHERE T.JOB_ID = ? ";
this.jdbcTemplateQuartz.update(upSql, new Object[] {scheduleJob.getJobId()});
});
return scheduleJobList.size();
}
@Override
public Job findJob(Long jobId) {
// TODO Auto-generated method stub
String sql = "SELECT T.JOB_ID,T.BEAN_NAME,T.METHOD_NAME,T.PARAMS,T.CRON_EXPRESSION,T.START_TIME,T.END_TIME,T.STATUS,T.REMARK,T.CREATE_TIME FROM QRTZ_JOB T WHERE T.JOB_ID = ? ";
List<Job> scheduleJobList = this.jdbcTemplateQuartz.query(sql, new Object[] {jobId}, new BeanPropertyRowMapper<Job>(Job.class));
return scheduleJobList.get(0);
}
@Override
public void createJob(Job job) {
// TODO Auto-generated method stub
}
@Override
public void updateJob(Job job) {
// TODO Auto-generated method stub
}
@Override
public void deleteJobs(String[] jobIds) {
// TODO Auto-generated method stub
}
@Override
public void updateBatch(String jobIds, String status) {
// TODO Auto-generated method stub
}
@Override
public void run(String jobIds) {
// TODO Auto-generated method stub
String[] list = jobIds.split(",");
Arrays.stream(list).forEach(jobId -> ScheduleUtils.run(scheduler, this.findJob(Long.valueOf(jobId))));
}
@Override
public void pause(String jobIds) {
// TODO Auto-generated method stub
}
@Override
public void resume(String jobIds) {
// TODO Auto-generated method stub
}
}
JobLogService.java
java
public interface JobLogService {
/**
* 保存定时任务日志
*
* @param log 定时任务日志
*/
void saveJobLog(JobLog log);
}
JobLogServiceImpl.java
java
@Service("JobLogService")
public class JobLogServiceImpl implements JobLogService {
@Autowired
private JdbcTemplate jdbcTemplateQuartz;
@Override
public void saveJobLog(JobLog jobLog) {
String sql = "INSERT INTO QRTZ_JOB_LOG (JOB_ID,BEAN_NAME,METHOD_NAME,PARAMS,STATUS,ERROR,TIMES) VALUES (?,?,?,?,?,?,?)";
this.jdbcTemplateQuartz.update(sql,
new Object[] {jobLog.getJobId(),jobLog.getBeanName(),
jobLog.getMethodName(),jobLog.getParams(),jobLog.getStatus(),
jobLog.getErrorInfo(),jobLog.getTimes()
});
}
}
6.5 定时任务控制
6.5.1 基础工具类
工具类 SpringContextUtil.java
java
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz){
return applicationContext.getBean(clazz);
}
public static <T> T getBean(String name, Class<T> requiredType) {
return applicationContext.getBean(name, requiredType);
}
public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
public static boolean isSingleton(String name) {
return applicationContext.isSingleton(name);
}
public static Class<?> getType(String name) {
return applicationContext.getType(name);
}
}
DateUtil.java
java
public class DateUtil {
public static final String FULL_TIME_SPLIT_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static Date getFormatDate(String dateStr) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(FULL_TIME_SPLIT_PATTERN);
return sdf.parse(dateStr);
}
}
6.5.2 定时任务执行类
ScheduleRunnable.java
java
@Slf4j
public class ScheduleRunnable implements Runnable {
private Object target;
private Method method;
private String params;
ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
this.target = SpringContextUtil.getBean(beanName);
this.params = params;
if (StringUtils.isNotBlank(params)) {
this.method = target.getClass().getDeclaredMethod(methodName, String.class);
} else {
this.method = target.getClass().getDeclaredMethod(methodName);
}
}
@Override
public void run() {
try {
ReflectionUtils.makeAccessible(method);
if (StringUtils.isNotBlank(params)) {
method.invoke(target, params);
} else {
method.invoke(target);
}
} catch (Exception e) {
log.error("执行定时任务失败", e);
}
}
}
6.5.3 定时任务工具类
ScheduleUtils.java
java
@Slf4j
public class ScheduleUtils {
private static final String JOB_NAME_PREFIX = "TASK_";
/**
* 获取触发器 key
*/
private static TriggerKey getTriggerKey(Long jobId) {
return TriggerKey.triggerKey(JOB_NAME_PREFIX + jobId);
}
/**
* 获取jobKey
*/
private static JobKey getJobKey(Long jobId) {
return JobKey.jobKey(JOB_NAME_PREFIX + jobId);
}
/**
* 获取表达式触发器
*/
public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId) {
try {
return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
} catch (SchedulerException e) {
log.error("获取Cron表达式失败", e);
}
return null;
}
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, Job scheduleJob) {
try {
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId()))
.build();
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().startAt(DateUtil.getFormatDate(scheduleJob.getStartTime())).endAt(DateUtil.getFormatDate(scheduleJob.getEndTime()))
.withIdentity(getTriggerKey(scheduleJob.getJobId()))
.withSchedule(scheduleBuilder).build();
// 放入参数,运行时的方法可以获取
trigger.getJobDataMap().put(Job.JOB_PARAM_KEY, scheduleJob);
trigger.getJobDataMap().put("jobId", scheduleJob.getJobId());
trigger.getJobDataMap().put("beanName", scheduleJob.getBeanName());
trigger.getJobDataMap().put("methodName", scheduleJob.getMethodName());
trigger.getJobDataMap().put("params", scheduleJob.getParams());
scheduler.scheduleJob(jobDetail, trigger);
// 暂停任务
if (scheduleJob.getStatus().equals(Job.ScheduleStatus.PAUSE.getValue())) {
pauseJob(scheduler, scheduleJob.getJobId());
}else if (scheduleJob.getStatus().equals(Job.ScheduleStatus.DEL.getValue())) {
deleteScheduleJob(scheduler, scheduleJob.getJobId());
}
} catch (SchedulerException | ParseException e) {
log.error("创建定时任务失败", e);
}
}
/**
* 更新定时任务
*/
public static void updateScheduleJob(Scheduler scheduler, Job scheduleJob) {
try {
deleteScheduleJob(scheduler, scheduleJob.getJobId());
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId()))
.build();
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().startAt(DateUtil.getFormatDate(scheduleJob.getStartTime())).endAt(DateUtil.getFormatDate(scheduleJob.getEndTime()))
.withIdentity(getTriggerKey(scheduleJob.getJobId()))
.withSchedule(scheduleBuilder).build();
// 放入参数,运行时的方法可以获取
trigger.getJobDataMap().put(Job.JOB_PARAM_KEY, scheduleJob);
trigger.getJobDataMap().put("jobId", scheduleJob.getJobId());
trigger.getJobDataMap().put("beanName", scheduleJob.getBeanName());
trigger.getJobDataMap().put("methodName", scheduleJob.getMethodName());
trigger.getJobDataMap().put("params", scheduleJob.getParams())
scheduler.scheduleJob(jobDetail, trigger);
// 暂停任务
if (scheduleJob.getStatus().equals(Job.ScheduleStatus.PAUSE.getValue())) {
pauseJob(scheduler, scheduleJob.getJobId());
}else if (scheduleJob.getStatus().equals(Job.ScheduleStatus.DEL.getValue())) {
deleteScheduleJob(scheduler, scheduleJob.getJobId());
}
} catch (SchedulerException | ParseException e) {
log.error("更新定时任务失败", e);
}
}
/**
* 立即执行任务
*/
public static void run(Scheduler scheduler, Job scheduleJob) {
try {
// 参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(Job.JOB_PARAM_KEY, scheduleJob);
dataMap.put("jobId", scheduleJob.getJobId());
dataMap.put("beanName", scheduleJob.getBeanName());
dataMap.put("methodName", scheduleJob.getMethodName());
dataMap.put("params", scheduleJob.getParams());
scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap);
} catch (SchedulerException e) {
log.error("执行定时任务失败", e);
}
}
/**
* 暂停任务
*/
public static void pauseJob(Scheduler scheduler, Long jobId) {
try {
scheduler.pauseJob(getJobKey(jobId));
} catch (SchedulerException e) {
log.error("暂停定时任务失败", e);
}
}
/**
* 恢复任务
*/
public static void resumeJob(Scheduler scheduler, Long jobId) {
try {
scheduler.resumeJob(getJobKey(jobId));
} catch (SchedulerException e) {
log.error("恢复定时任务失败", e);
}
}
/**
* 删除定时任务
*/
public static void deleteScheduleJob(Scheduler scheduler, Long jobId) {
try {
scheduler.deleteJob(getJobKey(jobId));
} catch (SchedulerException e) {
log.error("删除定时任务失败", e);
}
}
}
6.5.4 Job
ScheduleJob.java
java
@Slf4j
public class ScheduleJob extends QuartzJobBean {
private ThreadPoolTaskExecutor scheduleJobExecutorService = SpringContextUtil.getBean("scheduleJobExecutorService", ThreadPoolTaskExecutor.class);
@Autowired
private JobService jobService;
@SneakyThrows
@Override
protected void executeInternal(JobExecutionContext context) {
JobDataMap jobDataMap = context.getTrigger().getJobDataMap();
Job scheduleJob = new Job();
scheduleJob.setJobId((Long)context.getMergedJobDataMap().get("jobId"));
scheduleJob.setBeanName((String)context.getMergedJobDataMap().get("beanName"));
scheduleJob.setMethodName((String)context.getMergedJobDataMap().get("methodName"));
scheduleJob.setParams((String)context.getMergedJobDataMap().get("params"));
// 获取spring bean
JobLogService scheduleJobLogService = SpringContextUtil.getBean(JobLogService.class);
JobLog jobLog = new JobLog();
jobLog.setJobId(scheduleJob.getJobId());
jobLog.setBeanName(scheduleJob.getBeanName());
jobLog.setMethodName(scheduleJob.getMethodName());
jobLog.setParams(scheduleJob.getParams());
jobLog.setCreateTime(new Date());
long startTime = System.currentTimeMillis();
try {
// 执行任务
log.info("任务准备执行,任务ID:{}", scheduleJob.getJobId());
ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getBeanName(), scheduleJob.getMethodName(),
scheduleJob.getParams());
Future<?> future = scheduleJobExecutorService.submit(task);
future.get();
long times = System.currentTimeMillis() - startTime;
jobLog.setTimes(times);
// 任务状态 0:成功 1:失败
jobLog.setStatus(JobLog.JOB_SUCCESS);
log.info("任务执行完毕,任务ID:{} 总共耗时:{} 毫秒", scheduleJob.getJobId(), times);
} catch (Exception e) {
log.error("任务执行失败,任务ID:" + scheduleJob.getJobId(), e);
long times = System.currentTimeMillis() - startTime;
jobLog.setTimes(times);
// 任务状态 0:成功 1:失败
jobLog.setStatus(JobLog.JOB_FAIL);
jobLog.setErrorInfo(StringUtils.substring(e.toString(), 0, 2000));
} finally {
scheduleJobLogService.saveJobLog(jobLog);
}
}
}
6.6 定时任务读取配置task
LoadUpdateTask.java
java
@Slf4j
@Component
public class LoadUpdateTask {
@Autowired
private JobService jobService;
public void loadUpdateJob() {
log.info("加载需要修改的job-开始~~~");
log.info("已加载{}个job~~~",jobService.init());
log.info("加载需要加载的job-结束~~~");
}
}
6.7 测试
2020-10-20 17:01:45.050 INFO 5480 --- eduler_Worker-1 com.wno704.boot.util.ScheduleJob : 任务准备执行,任务ID:1
2020-10-20 17:01:45.055 INFO 5480 --- fb-Job-Thread1 com.wno704.boot.job.LoadUpdateTask : 加载需要修改的job-开始~~~
2020-10-20 17:01:45.056 INFO 5480 --- fb-Job-Thread1 com.wno704.boot.job.LoadUpdateTask : 已加载0个job~~~
2020-10-20 17:01:45.057 INFO 5480 --- fb-Job-Thread1 com.wno704.boot.job.LoadUpdateTask : 加载需要加载的job-结束~~~
2020-10-20 17:01:45.057 INFO 5480 --- eduler_Worker-1 com.wno704.boot.util.ScheduleJob : 任务执行完毕,任务ID:1 总共耗时:7 毫秒
查看记录表

注意:如果定时器无法加载,有可能是已经加载,或者失效等,需要清空定时器相关表,执行以下删除脚本:
sql
delete from qrtz_blob_triggers;
delete from qrtz_calendars;
delete from qrtz_cron_triggers;
delete from qrtz_fired_triggers;
delete from qrtz_job_log;
delete from qrtz_locks;
delete from qrtz_paused_trigger_grps;
delete from qrtz_scheduler_state;
delete from qrtz_simple_triggers;
delete from qrtz_simprop_triggers;
delete from qrtz_triggers;
delete from qrtz_job_details;