我们都知道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,从而形成了一个完整的动态添加定时和删除定时任务