SpringBoot集成Quartz動態定時任務
項目中需要用到定時任務,考慮了下java方面定時任務無非就三種:
- 用Java自帶的timer類。稍微看了一下,可以實現大部分的指定頻率的任務的調度(timer.schedule()),也可以實現關閉和開啟(timer.cancle)。但是用其來實現某天的某個時間或者某月的某一天調度任務有點不方便。
- 采用Quartz 調度器實現。這是一個功能很強大的開源的專門用於定時任務調度的框架,也很好的和springboot整合,缺點:配置復雜,需要花費一定的時間去了解和研究。
- spring3.0以后自帶的scheduletask任務調度,可以實現quartz的大部分功能,不需要額外引用jar,也不需要另外配置。而且支持注解和配置文件兩種。
鑒於項目有些地方要考慮動態管理定時任務的,所以考慮吧quartz也集成進去,方便調用。
一、首先引入依賴(必需)
<!--任務調度相關依賴-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.3</version>
</dependency>
二、創建job 實例工廠
解決spring注入問題,如果使用默認會導致spring的@Autowired 無法注入問題
package com.qunyi.jifenzhi_zx.core.quartz.taskjobfactory;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
/**
* @author xujingyang
* @Description: 解決quartz無法注入spring bean問題
* @date 2018/5/30.
*/
@Component
public class TaskJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//調用父類的方法
Object jobInstance = super.createJobInstance(bundle);
//進行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
三、創建job管理類
對job進行增加,暫停,恢復,更新,刪除等操作
import com.qunyi.jifenzhi_zx.common.core.quartz.BaseTaskJob;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @author xujingyang
* @Description: task任務創建工具類
* @date 2018/5/30.
*/
@Component
public class QuartzJobManager {
private static final Logger logger = LoggerFactory.getLogger(QuartzJobManager.class);
private static QuartzJobManager jobUtil;
@Autowired
private Scheduler scheduler;
public QuartzJobManager() {
logger.info("init jobUtil");
jobUtil = this;
}
public static QuartzJobManager getInstance() {
logger.info("retun JobCreateUtil");
return QuartzJobManager.jobUtil;
}
/**
* 創建job
*
* @param clazz 任務類
* @param jobName 任務名稱
* @param jobGroupName 任務所在組名稱
* @param cronExpression cron表達式
* @throws Exception
* @author xujingyang
* @date 2018/5/30.
*/
public void addJob(Class clazz, String jobName, String jobGroupName, String cronExpression) throws Exception {
// 啟動調度器
scheduler.start();
//構建job信息
JobDetail jobDetail = JobBuilder.newJob(((BaseTaskJob) clazz.newInstance()).getClass()).withIdentity(jobName, jobGroupName).build();
//表達式調度構建器(即任務執行的時間)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表達式構建一個新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
.withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* 創建job,可傳參
*
* @param clazz 任務類
* @param jobName 任務名稱
* @param jobGroupName 任務所在組名稱
* @param cronExpression cron表達式
* @param argMap map形式參數
* @throws Exception
* @author xujingyang
* @date 2018/5/30.
*/
public void addJob(Class clazz, String jobName, String jobGroupName, String cronExpression, Map<String, Object> argMap) throws Exception {
// 啟動調度器
scheduler.start();
//構建job信息
JobDetail jobDetail = JobBuilder.newJob(((BaseTaskJob) clazz.newInstance()).getClass()).withIdentity(jobName, jobGroupName).build();
//表達式調度構建器(即任務執行的時間)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表達式構建一個新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
.withSchedule(scheduleBuilder).build();
//獲得JobDataMap,寫入數據
trigger.getJobDataMap().putAll(argMap);
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* 暫停job
*
* @param jobName 任務名稱
* @param jobGroupName 任務所在組名稱
* @throws SchedulerException
* @author xujingyang
* @date 2018/5/30.
*/
public void pauseJob(String jobName, String jobGroupName) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* 恢復job
*
* @param jobName 任務名稱
* @param jobGroupName 任務所在組名稱
* @throws SchedulerException
* @author xujingyang
* @date 2018/5/30.
*/
public void resumeJob(String jobName, String jobGroupName) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* job 更新,只更新頻率
*
* @param jobName 任務名稱
* @param jobGroupName 任務所在組名稱
* @param cronExpression cron表達式
* @throws Exception
* @author xujingyang
* @date 2018/5/30.
*/
public void updateJob(String jobName, String jobGroupName, String cronExpression) throws Exception {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
// 表達式調度構建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表達式重新構建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新設置job執行
scheduler.rescheduleJob(triggerKey, trigger);
}
/**
* job 更新,更新頻率和參數
*
* @param jobName 任務名稱
* @param jobGroupName 任務所在組名稱
* @param cronExpression cron表達式
* @param argMap 參數
* @throws Exception
* @author xujingyang
* @date 2018/5/30.
*/
public void updateJob(String jobName, String jobGroupName, String cronExpression, Map<String, Object> argMap) throws Exception {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
// 表達式調度構建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表達式重新構建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//修改map
trigger.getJobDataMap().putAll(argMap);
// 按新的trigger重新設置job執行
scheduler.rescheduleJob(triggerKey, trigger);
}
/**
* job 更新,只更新更新參數
*
* @param jobName 任務名稱
* @param jobGroupName 任務所在組名稱
* @param argMap 參數
* @throws Exception
* @author xujingyang
* @date 2018/5/30.
*/
public void updateJob(String jobName, String jobGroupName, Map<String, Object> argMap) throws Exception {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//修改map
trigger.getJobDataMap().putAll(argMap);
// 按新的trigger重新設置job執行
scheduler.rescheduleJob(triggerKey, trigger);
}
/**
* job 刪除
*
* @param jobName 任務名稱
* @param jobGroupName 任務所在組名稱
* @throws Exception
* @author xujingyang
* @date 2018/5/30.
*/
public void deleteJob(String jobName, String jobGroupName) throws Exception {
scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* 啟動所有定時任務
*
* @author xujingyang
* @date 2018/5/30.
*/
public void startAllJobs() {
try {
scheduler.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 關閉所有定時任務
*
* @author xujingyang
* @date 2018/5/30.
*/
public void shutdownAllJobs() {
try {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 獲取所有任務列表
*
* @return
* @throws SchedulerException
*/
public List<Map<String, Object>> getAllJob() throws SchedulerException {
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
List<Map<String, Object>> jobList = new ArrayList<>();
for (JobKey jobKey : jobKeys) {
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
Map<String, Object> job = new HashMap<>();
job.put("jobName", jobKey.getName());
job.put("jobGroupName", jobKey.getGroup());
job.put("trigger", trigger.getKey());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
job.put("jobStatus", triggerState.name());
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
String cronExpression = cronTrigger.getCronExpression();
job.put("cronExpression", cronExpression);
}
jobList.add(job);
}
}
return jobList;
}
}
四、增加quartz 屬性配置文件 quartz.properties
放置resource目錄下,此文件主要提供schedule自動注入提供屬性
# 固定前綴org.quartz
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
五、創建quartz的配置類QuartzrConfiguration,進行屬性配置
package com.qunyi.jifenzhi_zx.core.config;
import com.qunyi.jifenzhi_zx.core.quartz.taskjobfactory.TaskJobFactory;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.io.IOException;
/**
* @author xujingyang
* @Description: 任務調度配置
* @date 2018/5/30.
*/
@Configuration
public class QuartzConfiguration {
@Autowired
private DataSource dataSource;
@Autowired
private TaskJobFactory jobFactory;
@Bean(name = "SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
//獲取配置屬性
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的屬性被讀取並注入后再初始化對象
propertiesFactoryBean.afterPropertiesSet();
//創建SchedulerFactoryBean
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setQuartzProperties(propertiesFactoryBean.getObject());
factory.setJobFactory(jobFactory);
return factory;
}
/*
* 通過SchedulerFactoryBean獲取Scheduler的實例
*/
@Bean(name = "scheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}
六、創建基礎任務調度接口
package com.qunyi.jifenzhi_zx.core.quartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* @author xujingyang
* @Description: 基礎任務調度taskJob接口
* @date 2018/5/30
* <p>
*/
public interface BaseTaskJob extends Job {
void execute(JobExecutionContext context)
throws JobExecutionException;
}
七、創建任務實現類
package com.qunyi.jifenzhi_zx.module.taskJob;
import com.qunyi.jifenzhi_zx.core.quartz.BaseTaskJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class TestQuartz implements BaseTaskJob {
Logger logger = LoggerFactory.getLogger(this.getClass());
public int i = 0;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
i++;
logger.error("task2>>>>>>> " + i);
try {
// QuartzJobManager.getInstance().jobdelete(this.getClass().getSimpleName(),"ah");//執行完此任務就刪除自己
} catch (Exception e) {
e.printStackTrace();
}
}
}
八、創建controller測試動態添加任務
@PostMapping("/task")
public void task(HttpServletRequest request) throws Exception {
String name = PrimaryKeyUtil.nextId();
QuartzJobManager.getInstance().addJob(TestQuartz.class, name, "*/1 * * * * ?");
}
九、訪問測試結果
總結及注意:
這種任務執行時是整個類都要初始化一遍的,不像spring的schedule只初始執行方法,這種每次執行類中的變量都會初始化。
添加新任務調度時,只需要新建類然后繼承任務接口實現方法即可,這樣在添加任務的時候只需要傳新建類的class就可以了。
傳同一個class的時候,是同一個任務方法,只不過新建了開了一個線程來執行而已。