Quartz 是开源任务调度框架中的翘首,它提供了强大任务调度机制,同时保持了使用的简单性。
Quartz 允许开发人员灵活地定义触发器的调度时间表,并可以对触发器和任务进行关联映射。
此外,Quartz提供了调度运行环境的持久化机制,可以保存并恢复调度现场,即使系统因故障关闭,任务调度现场数据并不会丢失。
此外,Quartz还提供了组件式的侦听器、各种插件、线程池等功能。
Spring中使用Quartz的3种方法(MethodInvokingJobDetailFactoryBean,implements Job,extends QuartzJobBean)
以下介绍一下实现job接口的方法,通过此方法可以动态启动,暂定,添加,删除定时功能,可传参数。
所有数据全部持久化到数据表中,不再需要XML配置文件存储数据。quartz已经封装了持久化方法。数据表用的MYSQL见附件
package com.berheley.bi.basic.timer;
import java.util.Map;
import org.quartz.JobExecutionContext;
import com.berheley.bi.basic.exp.BusinessException;
/**
* 类功能描述:定时任务需要实现此接口
*
* @author <a href="mailto:qingyu.meng21@gmail.com">mengqingyu </a>
* Create: 2010-1-3 上午09:18:51
* Description:
*/
public interface ITimerJob
{
/**
* 定时器执行的任务
* @param context
* @throws Exception
*/
void jtaTimerExecute(JobExecutionContext context, Map<?, ?> parameters)throws BusinessException;
}
package com.berheley.bi.basic.timer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.webframe.web.util.WebFrameUtils;
import com.berheley.bi.basic.exp.BusinessException;
/**
* 类功能描述:定时执行的任务,发布表单
*
* @author <a href="mailto:qingyu.meng21@gmail.com">mengqingyu </a>
* Create: 2009-12-31 上午12:31:02
* Description:
*/
public class TimerDataJob implements Job {
private ITimerJob timerJob;
protected Log log = LogFactory.getLog(getClass());
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap parameters = context.getJobDetail().getJobDataMap();
try {
Object beanName = parameters.get("beanName");
if (beanName == null) {
log.error("JobDataMap 中'beanName'属性值为null!");
return;
}
timerJob = (ITimerJob) WebFrameUtils.getBean(beanName.toString());
timerJob.jtaTimerExecute(context, parameters);
} catch (NoSuchBeanDefinitionException e) {
log.error("任务ITimerJob接口的实现类(" + e.getBeanName() + ")不存在!");
} catch (BusinessException e) {
log.error(e.getMessage(), e);
}
}
}
import java.util.Map;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import com.berheley.bi.basic.exp.BusinessException;
/**
* 类功能描述:定时调度后台管理
*
* @author <a href="mailto:qingyu.meng21@gmail.com">mengqingyu </a> Create: 2010-1-3 上午09:18:51
* Description:
*/
public interface ITimerManageService {
/**
* 获得scheduler
*/
Scheduler getScheduler();
/**
*
* @function:获得任务
* @param jobName
* @param jobGroup
* @return
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:29:03
*/
JobDetail getJob(String jobName, String jobGroup) throws BusinessException;
/**
*
* @function:添加任务
* @param jobDetail
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:29:15
*/
void addJob(JobDetail jobDetail) throws BusinessException;
/**
*
* @function:删除任务
* @param jobName
* @param jobGroup
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:29:31
*/
void deleteJob(String jobName, String jobGroup) throws BusinessException;
/**
*
* @function:立即运行
* @param jobName
* @param jobGroup
* @author: mengqingyu 2012-5-24 上午11:00:20
*/
void addRunNowTrigger(String jobName, String jobGroup) throws BusinessException ;
/**
*
* @function:重置定时器
* @param jobName
* @param jobGroup
* @param trigger
* @throws BusinessException
* @author: mengqingyu 2012-11-30 下午05:22:48
*/
public void rescheduleJob(String triggerName, String triggerGroup, Trigger trigger) throws BusinessException;
/**
*
* @function:获得定时器
* @param jobName
* @param jobGroup
* @return
* @throws BusinessException
* @author: mengqingyu 2012-11-30 下午05:17:09
*/
Trigger getTrigger(String jobName, String jobGroup) throws BusinessException;
/**
*
* @function:添加定时
* @param trigger
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:29:41
*/
void addTrigger(Trigger trigger) throws BusinessException;
/**
* 删除定时
*
* @param triggerName
* @param group
*/
void deleteTrigger(String triggerName, String group) throws BusinessException ;
/**
* 暂停定时
*
* @param triggerName
* @param group
*/
void updatePauseTrigger(String triggerName, String group) throws BusinessException ;
/**
* 恢复Trigger
*
* @param triggerName
* @param group
*/
void updateResumeTrigger(String triggerName, String group) throws BusinessException ;
/**
*
* @function:保存、修改任务
* @param jobName
* @param jobGroup
* @param description
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:32:35
*/
void saveOrUpdateJob(String jobName, String jobGroup, String description) throws BusinessException;
/**
*
* @function:批量删除任务
* @param jobNames
* @param jobGroup
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:33:11
*/
void deleteJobs(String jobNames, String jobGroup) throws BusinessException;
/**
*
* @function:查询任务参数
* @param jobName
* @param jobGroup
* @return
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:34:20
*/
String findJobParams(String jobName, String jobGroup) throws BusinessException;
/**
*
* @function:保存任务参数
* @param jobName
* @param jobGroup
* @param key
* @param value
* @return
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:38:17
*/
String saveJobParam(String jobName, String jobGroup, String key, String value) throws BusinessException;
/**
*
* @function:删除任务参数
* @param jobName
* @param jobGroup
* @param keys
* @return
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:38:26
*/
String deleteJobParams(String jobName, String jobGroup, String keys) throws BusinessException;
/**
*
* @function:保存cron
* @param params
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:38:41
*/
void saveCron(Map<String, Object> params) throws BusinessException;
/**
*
* @function:保存simple
* @param params
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:40:39
*/
void saveSimple(Map<String, Object> params) throws BusinessException;
/**
*
* @function:批量删除定时
* @param jobName
* @param group
* @param triggerNames
* @throws BusinessException
* @author: mengqingyu 2012-10-22 下午03:41:42
*/
void deleteTriggers(String jobName, String group, String triggerNames) throws BusinessException;
/**
* 根据名称和组别启动和暂停Tigger
*
* @param triggerName
* @param group
*/
String updateStartOrStop(String jobName, String jobGroup) throws BusinessException ;
}
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.webframe.core.util.DateUtils;
import com.berheley.bi.basic.exp.BusinessException;
import com.berheley.bi.basic.util.UUIDGenerator;
/**
* 类功能描述:通用定时功能
*
* @author <a href="mailto:qingyu.meng21@gmail.com">mengqingyu </a>
* @version $Id: codetemplates.xml,v 1.1 2009/03/06 01:13:01 mengqingyu Exp $ Create: 2011-6-1
* 下午05:03:06
*/
@Service
public class TimerManageService implements ITimerManageService {
@Autowired
protected Scheduler scheduler;
public Scheduler getScheduler() {
return scheduler;
}
@Override
public JobDetail getJob(String jobName, String jobGroup) throws BusinessException{
JobDetail jobDetail;
try {
jobDetail = scheduler.getJobDetail(jobName, jobGroup);
} catch (SchedulerException e) {
throw new BusinessException("获取任务失败", e);
}
return jobDetail;
}
@Override
public void addJob(JobDetail jobDetail) throws BusinessException{
try {
scheduler.addJob(jobDetail, true);
} catch (SchedulerException e) {
throw new BusinessException("添加任务失败", e);
}
}
@Override
public void deleteJob(String jobName, String jobGroup) throws BusinessException{
try {
scheduler.deleteJob(jobName, jobGroup);
} catch (SchedulerException e) {
throw new BusinessException("删除任务失败", e);
}
}
@Override
public void addRunNowTrigger(String jobName, String jobGroup) throws BusinessException{
try {
scheduler.triggerJob(jobName, jobGroup);
} catch (SchedulerException e) {
throw new BusinessException("立即运行任务失败", e);
}
}
@Override
public void rescheduleJob(String triggerName, String triggerGroup, Trigger trigger) throws BusinessException{
try {
scheduler.rescheduleJob(triggerName,triggerGroup,trigger);
} catch (SchedulerException e) {
throw new BusinessException("重置定时器失败", e);
}
}
@Override
public Trigger getTrigger(String triggerName, String triggerGroup) throws BusinessException{
Trigger trigger;
try {
trigger = scheduler.getTrigger(triggerName,triggerGroup);
} catch (SchedulerException e) {
throw new BusinessException("获取定时器失败", e);
}
return trigger;
}
@Override
public void addTrigger(Trigger trigger) throws BusinessException {
try {
trigger.setVolatility(false);
scheduler.scheduleJob(trigger);
} catch (SchedulerException e) {
throw new BusinessException("添加定时器失败", e);
}
}
@Override
public void deleteTrigger(String triggerName, String group) throws BusinessException{
this.updatePauseTrigger(triggerName, group);
try {
scheduler.unscheduleJob(triggerName, group);
} catch (SchedulerException e) {
throw new BusinessException("删除定时任务失败", e);
}
}
@Override
public void updatePauseTrigger(String triggerName, String group) throws BusinessException{
try {
scheduler.pauseTrigger(triggerName, group);
} catch (SchedulerException e) {
throw new BusinessException("停止定时任务失败", e);
}
}
@Override
public void updateResumeTrigger(String triggerName, String group) throws BusinessException{
try {
scheduler.resumeTrigger(triggerName, group);
} catch (SchedulerException e) {
throw new BusinessException("恢复定时任务失败", e);
}
}
@Override
public void saveOrUpdateJob(String jobName, String jobGroup, String description) throws BusinessException {
JobDetail jobDetail;
if(StringUtils.isBlank(jobName)){
jobName = UUIDGenerator.getUUID();
try {
jobDetail = new JobDetail(jobName, jobGroup, Class.forName("com.berheley.bi.basic.timer.TimerDataJob"));
} catch (ClassNotFoundException e) {
throw new BusinessException("添加修改任务失败", e);
}
}
else {
jobDetail = this.getJob(jobName, jobGroup);
}
jobDetail.setDescription(description);
this.addJob(jobDetail);
}
@Override
public void deleteJobs(String jobNames, String group) throws BusinessException {
String[] jobs = jobNames.split(",");
String[] groups = group.split(",");
for(int i=0;i<jobs.length;i++){
this.deleteJob(jobs[i], groups[i]);
}
}
public String paramMapToList(JobDataMap params) {
ArrayList<List<String>> listJobData = new ArrayList<List<String>>();
for (Object e: params.entrySet()) {
Map.Entry<?, ?> entry = (Entry<?, ?>) e;
ArrayList<String> list = new ArrayList<String>();
list.add("'" + entry.getKey() + "'");
list.add("'" + entry.getValue() + "'");
listJobData.add(list);
}
return listJobData.toString();
}
@Override
public String findJobParams(String jobName, String jobGroup) throws BusinessException {
JobDetail jobDetail = this.getJob(jobName, jobGroup);
return jobDetail == null?"{}":paramMapToList(jobDetail.getJobDataMap());
}
@Override
public String saveJobParam(String jobName, String jobGroup, String key, String value) throws BusinessException {
JobDetail jobDetail = this.getJob(jobName, jobGroup);
JobDataMap params = jobDetail.getJobDataMap();
params.put(key, value);
this.addJob(jobDetail);
return paramMapToList(params);
}
@Override
public String deleteJobParams(String jobName, String jobGroup, String keys) throws BusinessException {
JobDetail jobDetail = this.getJob(jobName, jobGroup);
JobDataMap params = jobDetail.getJobDataMap();
String[] key = keys.split(",");
for(int i=0;i<key.length;i++){
params.remove(key[i]);
}
this.addJob(jobDetail);
return paramMapToList(params);
}
public void initTrigger(Trigger trigger, Map<String, Object> params) {
trigger.setDescription(params.get("label").toString());
trigger.setJobName(params.get("jobName").toString());
trigger.setJobGroup(params.get("group").toString());
String startTime = params.get("startTime")==null?"":params.get("startTime").toString();
String stopTime = params.get("stopTime")==null?"":params.get("stopTime").toString();
if (!StringUtils.isBlank(startTime)) {
trigger.setStartTime(DateUtils.parseStringToDate(startTime,"yyyy-MM-dd HH:mm:ss"));
}
if (!StringUtils.isBlank(stopTime)) {
trigger.setEndTime(DateUtils.parseStringToDate(stopTime,"yyyy-MM-dd HH:mm:ss"));
}
}
@Override
public void saveCron(Map<String, Object> params) throws BusinessException {
Trigger trigger = null;
try {
trigger = new CronTrigger(UUIDGenerator.getUUID(), params.get("group").toString(), params.get("cronExpression").toString());
} catch (ParseException e) {
throw new BusinessException("添加cron失败", e);
}
this.initTrigger(trigger, params);
this.addTrigger(trigger);
}
@Override
public void saveSimple(Map<String, Object> params) throws BusinessException {
String countStr = params.get("repeatCount").toString();
int repeatCount = countStr.equals("")?SimpleTrigger.REPEAT_INDEFINITELY:Integer.parseInt(countStr);
long repeatInterval = Long.parseLong(params.get("repeatInterval").toString());
Trigger trigger = new SimpleTrigger(UUIDGenerator.getUUID(), params.get("group").toString(), repeatCount, repeatInterval);
this.initTrigger(trigger, params);
this.addTrigger(trigger);
}
@Override
public void deleteTriggers(String jobName, String group, String triggerNames) throws BusinessException {
String[] trigger = triggerNames.split(",");
for(int i=0;i<trigger.length;i++){
this.deleteTrigger(trigger[i], group);
}
}
@Override
public String updateStartOrStop(String triggerName, String group) throws BusinessException {
int flag = -1;
try {
flag = scheduler.getTriggerState(triggerName,group);
switch (flag) {
case 0 :
scheduler.pauseTrigger(triggerName, group);
flag = 1;
break;
case 1 :
scheduler.resumeTrigger(triggerName, group);
flag = 0;
break;
}
} catch (SchedulerException e) {
throw new BusinessException("修改定时状态失败", e);
}
return "{state:'"+flag+"'}";
}
}
这种配置就是对quartz的一种简单的使用了,调度任务会在spring启动的时候加载到内存中,按照bjcronTrigger中定义的crontrigger定义的时间按时触发调度任务。
但是这是quartz使用“内存”方式的一种配置,也比较常见,当然对于不使用spring的项目,也可以单独整合quartz。
方法也比较简单,可以从quartz的doc中找到配置方式,或者看一下《Quartz Job Scheduling Framework 》(附件中可下载)这本书中的例子。
但是对于想持久化调度任务的状态,并且灵活调整调度时间的方式来说,上面的内存方式就不能满足要求了,正如本文开始我遇到的情况,需要采用数据库方式集成quartz,
这部分集成其实在《Quartz Job Scheduling Framework 》中也有较为详细的介绍,当然doc文档中也有,
但是缺乏和spring集成的实例,我在这里把我在项目中在spring配置quartz数据库存储方式的配置也写一下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean name="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource"> <ref bean="dataSource" /> </property> <property name="applicationContextSchedulerContextKey" value="BI-Scheduler" /> <property name="configLocation" value="classpath:quartz.properties" /> </bean> </beans>
属性说明:
dataSource:项目中用到的数据源,里面包含了quartz用到的12张数据库表;
schedulerName:调度器名,我理解主要在调度集群的时候会有用,如果出现多个调度器实例的时候可以用来进行区分,详细看一下《Quartz Job Scheduling Framework 》;
configLocation:用于指明quartz的配置文件的位置,如果不用spring配置quartz的话,本身quartz是通过一个配置文件进行配置的
,默认名称是quartz.properties,里面配置的参数在quartz的doc文档中都有介绍,可以调整quartz,我在项目中也用这个文件部分的配置了一些属性,代码如下:
#============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName = TestScheduler org.quartz.scheduler.instanceId = AUTO #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 5 org.quartz.threadPool.threadPriority = 4 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold = 60000 #org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX ##org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate #org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate #org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.jobStore.isClustered = false #============================================================================ # Configure Plugins #============================================================================ org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin #org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin # init plugin will load jobs.xml as a classpath resource i.e. /jobs.xml if not found on file system #org.quartz.plugin.jobInitializer.fileName=jobs.xml #org.quartz.plugin.jobInitializer.overWriteExistingJobs = false #org.quartz.plugin.jobInitializer.failOnFileNotFound = false