年前做了个触发器管理,将以前设计不合理的地方修改掉了。
所谓动态创建,就是触发器的时间、触发器的任务由用户自己去设定,当然这个任务还是需要开发人员手动去写。
以前同事做的触发器管理,将某个任务写死,没法做到扩展,也没有持久化方面的考虑
后来参考 http://sundoctor.iteye.com/blog/399980 中提到的持久化方法,进行修改,已经满足要求。
本文使用的是quartz的1.7版本,quartz好像是分为两部分,一部分是任务(job)一个是触发器(trigger);trigger运行过程中,达到触发时间条件,将会执行job。
触发器管理模块需要提供触发器动态创建、删除、暂停、恢复功能,并结合spring做到job的扩展
在与spring整合时,有两个配置项 jobDetails和triggers
<bean id="schedulerFactory" lazy-init='false'
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="applicationContextSchedulerContextKey"
value="applicationContextKey" />
<property name="configLocation"
value="classpath:quartz.properties" />
<property name="startupDelay" value="30" />
<!-- property name="overwriteExistingJobs" value="true" / -->
<!-- 可用的job列表 -->
<property name="jobDetails">
<list>
<!-- 示例 -->
<ref bean="avicinfo.test.testJobService" />
</list>
</property>
<!-- 默认启动的trigger列表 -->
<property name="triggers">
<list>
<ref bean="OnlineDocDeleteRubbishFilesJobTrigger" />
</list>
</property>
</bean>
triggers中配置的信息已经是完整的触发器了,包含时间、job等信息,这部分会在SchedulerFactoryBean生成Scheduler对象时,就已经加载到触发器列表中了。
jobDetails是job的配置部分,想要做到由用户选择触发什么job,就要从这里入手。
通过quartz的api了解到,我使用的这个版本,没有直接提供获取所有job的方法,而是可以通过查找所有jobGroupName,得到最后的job对象,而在我的触发器管理模块中,默认启动的trigger是不能被删除的,所以将这些trigger中job的group不做设置,即默认组(DEFAULT),而其他在jobDetails中配置的job需要强制设定组信息,并且还要实现固定的接口,以达到对这些动态创建trigger的管理。
获取可以用于动态创建的job
public List<JobDetail> getCanUseQrtzJobs() throws SchedulerException {
List<JobDetail> list = new ArrayList<JobDetail>();
String[] jobGroupNames = this.scheduler.getJobGroupNames();
for (String jobGroupName : jobGroupNames) {
// 去掉受保护的分组
if (!CANNOT_USE_GROUPS.contains(jobGroupName)) {
String[] jobNames = this.scheduler.getJobNames(jobGroupName);
for (String jobName : jobNames) {
JobDetail jd = this.scheduler.getJobDetail(jobName, jobGroupName);
// 获取实际调度对象
Object targetObject = jd.getJobDataMap().get("targetObject");
if (StaticMethod.hasInterface(targetObject.getClass(), CAN_USE_INTERFACES)) {
try {
Object obj = PropertyUtils.getProperty(targetObject, "triggerName");
if (StaticMethod.isEmpty(obj)) {
list.add(jd);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
}
return list;
}
job需要实现的接口,提供管理连接,和唯一标识这个trigger的属性,我使用的是triggerName,全局唯一。
/**
* @author lisen
* @date Jan 17, 2014 1:56:42 PM
*/
public interface SchedulerJobDetail extends Cloneable {
/**
* 获得展现页面的地址 不包括ip 端口号 工程名
*
* @return
*/
String getActionUrl();
/**
* 显示名称
*
* @return
*/
String getDisplayName();
/**
* 设置触发器名 对象唯一识别
*
* @param triggerName
*/
void setTriggerName(String triggerName);
/**
* 获取触发器名
*
* @return
*/
String getTriggerName();
}
在启动一个trigger时,可以根据选择的jobName,jobGroup,获取到这个job的配置对象,将它塞到创建的trigger对象中,但是在这个trigger执行完毕后,他的job也随之被释放了,持久化的对象被删除。而quartz提供的持久化方法,仅仅判断jobName和jobGroup,所以在创建时,要在获取配置中得到job的name做下手脚,让他变成一个唯一对象,这样,trigger的生命周期如何,都和我们配置job没有关系了
@Override
public void schedule(String triggerName, String triggerGroup, String jobDetailName,
String jobDetailGroup, String cronExpression, boolean overwriteExistingTrigger) {
try {
// 保证有这个job
JobDetail tjd = this.scheduler.getJobDetail(jobDetailName, jobDetailGroup);
if (!StaticMethod.isEmpty(tjd)) {
JobDetail jd = (JobDetail) tjd.clone();
// 处理获取到的job
handleJobDetail(jd, triggerName);
this.scheduler.addJob(jd, true);
Trigger t = this.scheduler.getTrigger(triggerName, triggerGroup);
if (!StaticMethod.isEmpty(t) && overwriteExistingTrigger) {
this.removeTrigger(triggerName, triggerGroup);
}
CronTrigger cronTrigger = new CronTrigger(triggerName, triggerGroup, jd.getName(),
jd.getGroup());
cronTrigger.setCronExpression(cronExpression);
this.scheduler.scheduleJob(cronTrigger);
}
} catch (Exception e) {
throw new SchedulerRuntimeException(e);
}
}
protected void handleJobDetail(JobDetail jd, String triggerName) {
jd.setDescription(jd.getName());
jd.setName(UUID.randomUUID().toString());
// 获取实际调度对象
Object targetObject = jd.getJobDataMap().get("targetObject");
if (StaticMethod.hasInterface(targetObject.getClass(), CAN_USE_INTERFACES)) {
try {
Object cloneTargetObject = MethodUtils.invokeMethod(targetObject, "clone", null);
PropertyUtils.setProperty(cloneTargetObject, "triggerName", triggerName);
jd.getJobDataMap().put("targetObject", cloneTargetObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
具体到job的配置
<bean id="avicinfo.test.testJobService"
class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject"
ref="avicinfo.test.testManageService">
</property>
<property name="group" value="test"></property>
<property name="targetMethod">
<value>testMethod</value>
</property>
<property name="concurrent" value="false" />
<property name="shouldRecover" value="true" />
</bean>
<bean id="avicinfo.test.testManageService"
class="com.avicinfo.v2.core.quartz.test.service.impl.MyJobService">
<property name="jdbcTemplate" value="jdbcTemplate"></property>
</bean>
job的实现
/**
* @author lisen
* @date Jan 17, 2014 2:07:18 PM
*/
public class MyJobService implements Serializable, SchedulerJobDetail, IMyJobService {
/**
*
*/
private static final long serialVersionUID = 5735518271578346479L;
private String jdbcTemplate;
private String triggerName;
/*
* (non-Javadoc)
*
* @see com.avicinfo.v2.core.quartz.test.service.bo.IMyJobService#testMethod()
*/
@Override
public void testMethod() {
String sql = "select sysdate from dual";
Object result = this.getJdbcTemplateObj().queryForObject(sql, Date.class);
System.out.println(result + " 测试job: " + this.getTriggerName() + " "
+ this.getClass().getName());
}
/*
* (non-Javadoc)
*
* @see com.avicinfo.v2.core.quartz.service.bo.SchedulerJobDetail#getActionUrl()
*/
@Override
public String getActionUrl() {
return "quartz/test/testAction.dd";
}
/*
* (non-Javadoc)
*
* @see com.avicinfo.v2.core.quartz.service.bo.SchedulerJobDetail#getDisplayName()
*/
@Override
public String getDisplayName() {
return "测试job";
}
/**
* @return the jdbcTemplate
*/
public String getJdbcTemplate() {
return jdbcTemplate;
}
/**
* @param jdbcTemplate
* the jdbcTemplate to set
*/
public void setJdbcTemplate(String jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplateObj() {
return SpringBean.getBean(JdbcTemplate.class, getJdbcTemplate());
}
/*
* (non-Javadoc)
*
* @see com.avicinfo.v2.core.quartz.test.service.bo.IMyJobService#saveConfig(java.util.Map)
*/
@Override
public void saveConfig(Map map) {
System.out.println("config:" + map);
// this.jdbcTemplate.saveConfig .....
}
/*
* (non-Javadoc)
*
* @see com.avicinfo.v2.core.quartz.service.bo.SchedulerJobDetail#setTriggerName(java.lang.String)
*/
@Override
public void setTriggerName(String triggerName) {
this.triggerName = triggerName;
}
/**
* @return the triggerName
*/
public String getTriggerName() {
return triggerName;
}
public Object clone() {
MyJobService copy;
try {
copy = (MyJobService) super.clone();
} catch (CloneNotSupportedException ex) {
throw new IncompatibleClassChangeError("Not Cloneable.");
}
return copy;
}
}