我們都知道Quatz配合Spring可以進行定時任務的功能開發,但是大家是否知道如何提供動態定時功能呢?在不重啓項目的情況下提供接口給其他調用者調用實現動態生成定時任務
公司因爲已經開始實施微服務,所以對各個服務拆分已經劃的比較細緻,具體微服務架構實施會在下一個博客放出,此文只討論動態定時功能,我們公司把定時功能已經獨立成了定時組件
定時組件提供了2個接口,一個動態添加定時任務,一個動態刪除定時任務,好了廢話不多,開始進入正題
首先在POM文件導入各種需要的Jar
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
上面是我們定時組件的表設計,這個表設計會將所有動態添加的定時任務已數據形式記錄到數據庫中,目的是爲了防止程序異常奔潰重啓等問題會丟失調度進程,再次重啓程序後可以保證原有有效的調度任務再次加載到Quatz調度任務中
我們的定時組件支持http/https和Restful請求,動態添加一次性或者週期性的定時任務,接收到動態添加請求數據入庫這種基礎代碼不放出來浪費大家時間了,下面放出調度的關鍵代碼
package com.bean; import java.io.Serializable; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; /** * 定時任務調度Bean * @author wmq * * 15618777630 */ @Entity @Table(name = "schedulejob") public class ScheduleJob implements Serializable{ /** * TODO - ScheduleJob.java long 2017年1月3日 mazkc */ private static final long serialVersionUID = 7251031173525766572L; public static final String JOB_PARAM = "jobParam"; /** 任務id */ private String jobId; /** 任務名稱 */ private String jobName; /** 任務分組 */ private String jobGroup; /** 任務狀態 0禁用 1啓用 2刪除*/ private String jobStatus; /** 任務運行時間表達式 */ private String cronExpression; /** 任務描述 */ private String remark; //任務創建時間 private Date createdate; //任務來源 private String resource; //執行任務的調度參數 private String task_params; //執行任務調度的URL private String task_url; //調度執行類型 http/https private String response_type; //觸發器名 private String trigget; //觸發器組名 private String triggetGroup; private int timer_type; private int is_rest; private String restful; /** * @return the jobId */ @Id @Column(name = "job_id", unique = true, nullable = false, length = 60) public String getJobId() { return jobId; } /** * @param jobId the jobId to set */ public void setJobId(String jobId) { this.jobId = jobId; } /** * @return the jobName */ @Column(name = "job_name") public String getJobName() { return jobName; } /** * @param jobName the jobName to set */ public void setJobName(String jobName) { this.jobName = jobName; } /** * @return the jobGroup */ @Column(name = "job_group") public String getJobGroup() { return jobGroup; } /** * @param jobGroup the jobGroup to set */ public void setJobGroup(String jobGroup) { this.jobGroup = jobGroup; } /** * @return the jobStatus */ @Column(name = "job_status") public String getJobStatus() { return jobStatus; } /** * @param jobStatus the jobStatus to set */ public void setJobStatus(String jobStatus) { this.jobStatus = jobStatus; } /** * @return the cronExpression */ @Column(name = "cron_expression") public String getCronExpression() { return cronExpression; } /** * @param cronExpression the cronExpression to set */ public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } /** * @return the remark */ public String getRemark() { return remark; } /** * @param remark the remark to set */ public void setRemark(String remark) { this.remark = remark; } /** * @return the createdate */ public Date getCreatedate() { return createdate; } /** * @param createdate the createdate to set */ public void setCreatedate(Date createdate) { this.createdate = createdate; } /** * @return the resource */ public String getResource() { return resource; } /** * @param resource the resource to set */ public void setResource(String resource) { this.resource = resource; } /** * @return the task_params */ public String getTask_params() { return task_params; } /** * @param task_params the task_params to set */ public void setTask_params(String task_params) { this.task_params = task_params; } /** * @return the task_url */ public String getTask_url() { return task_url; } /** * @param task_url the task_url to set */ public void setTask_url(String task_url) { this.task_url = task_url; } /** * @return the response_type */ public String getResponse_type() { return response_type; } /** * @param response_type the response_type to set */ public void setResponse_type(String response_type) { this.response_type = response_type; } /** * @return the trigget */ public String getTrigget() { return trigget; } /** * @param trigget the trigget to set */ public void setTrigget(String trigget) { this.trigget = trigget; } /** * @return the triggetGroup */ @Column(name = "trigget_group") public String getTriggetGroup() { return triggetGroup; } /** * @param triggetGroup the triggetGroup to set */ public void setTriggetGroup(String triggetGroup) { this.triggetGroup = triggetGroup; } /** * @return the timer_type */ public int getTimer_type() { return timer_type; } /** * @param timer_type the timer_type to set */ public void setTimer_type(int timer_type) { this.timer_type = timer_type; } /** * @return the is_rest */ public int getIs_rest() { return is_rest; } /** * @param is_rest the is_rest to set */ public void setIs_rest(int is_rest) { this.is_rest = is_rest; } /** * @return the restful */ public String getRestful() { return restful; } /** * @param restful the restful to set */ public void setRestful(String restful) { this.restful = restful; } }
package com.service.impl;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.stereotype.Service;
import com.bean.ScheduleJob;
/**
* 定時器動態調度主類
*
* @author wmq
*
* 15618777630
*/
@Service("quartzManager")
public class QuartzManager {
private Scheduler scheduler;
//調度數據是否在啓動時已被加載標示
public static boolean SCHEDULEJOB_LIST_BOOLEAN = false;
//定時啓動時查詢數據庫標示
public static boolean SCHEDULEJOB_ADD_BOOLEAN = false;
/**
*
*
TODO - 添加定時任務
@param jobClass 定是組件class
@param sc 定時組件基類bean
2017年1月4日
mazkc
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void addJob(Class jobClass,ScheduleJob sc) {
try {
// 任務名,任務組,任務執行類
JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(sc.getJobName(), sc.getJobGroup()).build();
// 觸發器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder
.newTrigger();
// 觸發器名,觸發器組
triggerBuilder.withIdentity(sc.getTrigget(), sc.getTriggetGroup());
triggerBuilder.startNow();
// 觸發器時間設定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(sc.getCronExpression()));
// 創建Trigger對象
CronTrigger trigger = (CronTrigger) triggerBuilder.build();
jobDetail.getJobDataMap().put(ScheduleJob.JOB_PARAM, sc);
// 調度容器設置JobDetail和Trigger
scheduler.scheduleJob(jobDetail, trigger);
// 啓動
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 修改一個任務的觸發時間
*
* @param jobName
* @param jobGroupName
* @param triggerName
* 觸發器名
* @param triggerGroupName
* 觸發器組名
* @param cron
* 時間設置,參考quartz說明文檔
*/
public void modifyJobTime(String jobName, String jobGroupName,
String triggerName, String triggerGroupName, String cron) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName,
triggerGroupName);
CronTrigger trigger = (CronTrigger) scheduler
.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cron)) {
/** 方式一 :調用 rescheduleJob 開始 */
// 觸發器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder
.newTrigger();
// 觸發器名,觸發器組
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();
// 觸發器時間設定
triggerBuilder.withSchedule(CronScheduleBuilder
.cronSchedule(cron));
// 創建Trigger對象
trigger = (CronTrigger) triggerBuilder.build();
// 方式一 :修改一個任務的觸發時間
scheduler.rescheduleJob(triggerKey, trigger);
/** 方式一 :調用 rescheduleJob 結束 */
/** 方式二:先刪除,然後在創建一個新的Job */
// JobDetail jobDetail =
// scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroupName));
// Class<? extends Job> jobClass = jobDetail.getJobClass();
// removeJob(jobName, jobGroupName, triggerName,
// triggerGroupName);
// addJob(jobName, jobGroupName, triggerName, triggerGroupName,
// jobClass, cron);
/** 方式二 :先刪除,然後在創建一個新的Job */
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 移除一個任務
*
* @param jobName
* @param jobGroupName
* @param triggerName
* @param triggerGroupName
*/
public void removeJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName,
triggerGroupName);
scheduler.pauseTrigger(triggerKey);// 停止觸發器
scheduler.unscheduleJob(triggerKey);// 移除觸發器
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 刪除任務
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description:啓動所有定時任務
*/
public void startJobs() {
try {
scheduler.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description:關閉所有定時任務
*/
public void shutdownJobs() {
try {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Scheduler getScheduler() {
return scheduler;
}
public void setScheduler(Scheduler scheduler) {
this.scheduler = scheduler;
}
}
package com.service.impl;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.bean.ScheduleJob;
import com.pub.FinalArgs;
import com.service.TaskJobForDBService;
@Service("initJob")
public class InitJob {
@Resource
private TaskJobForDBService taskJob;
@Resource
private QuartzManager quartzManager;
public static int indexJob = 0;
public static List<ScheduleJob> li = null;
public void init() throws Exception{
//如果靜態列表定時計劃數據爲空,那麼讀取數據庫
//判斷是否是從異常區再次進入加載
ScheduleJob sc = null;
if(false == FinalArgs.SCHEDULEJOB_LIST_BOOLEAN && null == li){
li = taskJob.initJobInfo();
FinalArgs.SCHEDULEJOB_LIST_BOOLEAN = true;
}
//如果執行計劃已被加載,那麼不在進行重複加載m
if(false == FinalArgs.SCHEDULEJOB_ADD_BOOLEAN){
if(null != li && li.size() > 0){
for(int i=indexJob;i<li.size();i++){
indexJob ++ ;
sc = li.get(i);
try{
quartzManager.addJob(UpboxJob.class, sc);
}catch(Exception e){
System.out.println(e.getMessage());
sc.setJobStatus("0");
taskJob.delinfoById(sc);
init();
}
}
}
li = null;
FinalArgs.SCHEDULEJOB_ADD_BOOLEAN = true;
}
}
}
上面這些代碼已經完成了動態調度,動態刪除,那麼如何發起數據庫保存的需要回調的事件呢,通過下面一個Service完成
package com.service.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.bean.ScheduleJob;
import com.org.pub.HttpUtil;
import com.org.pub.HttpsUtil;
import com.org.pub.PublicMethod;
import com.pub.Public_Cache;
import com.service.PublicService;
import com.service.TaskJobForDBService;
import com.util.ApplicationContextUtil;
@Component
@Service("upboxJob")
public class UpboxJob implements Job{
private HttpUtil http = new HttpUtil();
private HttpsUtil https = new HttpsUtil();
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
ScheduleJob sc = (ScheduleJob) arg0.getJobDetail().getJobDataMap().get(ScheduleJob.JOB_PARAM);
//HashMap<String,String> reMap = new HashMap<String,String>();//封裝訪問返回對象
Map<String,Object> map1 = null;
HashMap<String,String> map2 = null;
//由於在異步線程中,注入會失效,需要手動獲取Service
TaskJobForDBService task = (TaskJobForDBService) ApplicationContextUtil.getBean("taskJob");
PublicService publicService = (PublicService) ApplicationContextUtil.getBean("pubService");
QuartzManager quartzManager = (QuartzManager) ApplicationContextUtil.getBean("quartzManager");
try{
//是否是RestFul請求,不是
if(2 == sc.getIs_rest()){
if("http".equals(sc.getResponse_type())){ //普通http請求
if(null != sc.getTask_params() && !"".equals(sc.getTask_params())){ //是否帶參數請求
map1 = PublicMethod.parseJSON2Map(sc.getTask_params());
}
if(null == map1 || map1.size() == 0){
http.sendPost(sc.getTask_url(), null);
}else{
http.sendPost(sc.getTask_url(), map1);
}
System.out.println(PublicMethod.getDateToString(new Date(), "yyyy-MM-dd HH:mm:ss") + "------->" + sc.getTask_url());
}else if("https".equals(sc.getResponse_type())){ //普通https請求
if(null != sc.getTask_params() && !"".equals(sc.getTask_params())){
map2 = PublicMethod.parseJSON2HashMap2(sc.getTask_params());
}
if(null == map2 || map2.size() == 0){ //是否帶參數請求
https.httpRequest(sc.getTask_url(), "GET", null);
}else{
https.httpRequest(sc.getTask_url(), "GET", map2);
}
System.out.println(PublicMethod.getDateToString(new Date(), "yyyy-MM-dd HH:mm:ss") + "------->" + sc.getTask_url());
}
}else if(1 == sc.getIs_rest()){ //是RestFul請求
RestFulHttp(sc.getRestful(),sc.getTask_url(),map1,publicService);
System.out.println(PublicMethod.getDateToString(new Date(), "yyyy-MM-dd HH:mm:ss") + "------->" + sc.getTask_url());
}
//一次性定時任務處理結束後,需要更新數據庫狀態並且刪除調度任務,將一次性定時任務設置成0
if("1".equals(sc.getJobStatus()) && 2 == sc.getTimer_type()){
sc.setJobStatus("0");
task.delinfoById(sc);
quartzManager.removeJob(sc.getJobName(), sc.getJobGroup(), sc.getTrigget(), sc.getTriggetGroup());
}
}catch(Exception e){
e.printStackTrace();
//一次性定時任務處理結束後,需要更新數據庫狀態並且刪除調度任務,將一次性定時任務設置成0
if("1".equals(sc.getJobStatus()) && 2 == sc.getTimer_type()){
sc.setJobStatus("0");
try {
task.delinfoById(sc);
quartzManager.removeJob(sc.getJobName(), sc.getJobGroup(), sc.getTrigget(), sc.getTriggetGroup());
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
private void RestFulHttp(String type,String url,Map<String,Object> params,PublicService publicService){
if("post".equals(type)){
publicService.post(url, params);
}else if("get".equals(type)){
publicService.get(url, params);
}else if("put".equals(type)){
publicService.put(url, params);
}else if("delete".equals(type)){
publicService.delete(url, params);
}
}}
以上就是完整的項目代碼啦,可能有的同學還想要上面publicService的代碼,其實就是發起RestFul請求的通用代碼
@Override
public String post(String url, Map<String, Object> params) {
ResponseEntity<String> rss = request(url, HttpMethod.POST, params);
return rss.getBody();
}
@Override
public String get(String url, Map<String, Object> params) {
ResponseEntity<String> rss = request(url, HttpMethod.GET, params);
return rss.getBody();
}
@Override
public String delete(String url, Map<String, Object> params) {
ResponseEntity<String> rss = request(url, HttpMethod.DELETE, params);
return rss.getBody();
}
@Override
public String put(String url, Map<String, Object> params) {
ResponseEntity<String> rss = request(url, HttpMethod.PUT, params);
return rss.getBody();
}
細心的同學會發現其實整個流程就是通過動態的保存數據到數據庫,然後將動態請求的數據裝載到quartzManager這個調度Service中,由於在Spring初始化啓動時已經配置需要加載上面的initJob這個Service,而這個Service中有一段關鍵的處理代碼quartzManager.addJob(UpboxJob.class, sc); 這段代碼告訴整個Quatz調度當有合適的定時任務觸發時回調這個upboxJob這個Service,從而形成了一個完整的動態添加定時和刪除定時任務