应用场景: 集群环境. 在多台服务器布置了相同的程序,程序中都包含有相同的定时器任务.如果不做处理,则会出现定时任务重复执行的问题,一般的业务场景这个是必须要避免的.

解决办法: 一. 只在一台服务器发布带有定时器的程序,其他的全都去除定时任务再发布. (如果这台服务器宕掉了,那就悲剧了) ; 二. 采用Quratz开源定时任务框架,并配置集群.(好处在于spring集成了对Quratz的支持,如果你也用了spring+Quratz,往下看).

本用例采用框架版本: Spring 3.0 + Quratz 1.8.5 参考:http://soulshard.iteye.com/blog/337886

起初采用spring对Quratz的原生支持,但是报错. 异常信息: 

java.io.NotSerializableException: Unable to serialize JobDataMap for insertion into database because the value of property 'methodInvoker' is not serializable: org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean


据说是spring的bug,按照以上地址里给的解决办法,重写了MethodInvokingJobDetailFactoryBean. 但诡异的是,仍然报出 java.io.NotSerializableException,由于我的定时器里引用了Service层,这里抛出的是dataSource is not serializable.本以为是因为序列化的时候把Dao属性也获取并序列化了,所以就把getDao()都去掉了,但是还是会报错,看来不是按照这个逻辑执行程序的,按照上面地址里说的方案之一,说可以把service和Dao层都继承Serializable,但是我放弃了,因为我没办法把dataSource也继承 Serializable. 之后看了下面的留言,有一位仁兄写好了demo,并分享了出来.地址: OK,下面说详细代码.因为我下了那个demo之后还弄了好一会儿,有必要记录一下.

解决思路: 编写一个带有String类型属性的,实现Serializable的伪任务类,执行任务调度时,调用此类的实例,并注入真正要调用的类的Bean ID.也就是说,初始化任务的时候,注入的是这个类,而类中调用真正要执行的任务.从而避免了序列化问题.

伪任务类PseudoJob.java 

public class BootstrapJob implements Serializable{ 


private static final long serialVersionUID = 4195235887559214105L; 

private String targetJob ; 


public void execute(ApplicationContext cxt) { 

Job job = (Job)cxt.getBean(this.targetJob); 

job.execute() ; 

} 


public String getTargetJob() { 

return targetJob; 

} 


public void setTargetJob(String targetJob) { 

this.targetJob = targetJob; 

} 

} 


Job接口类:定时任务类必须继承Job接口 

public interface Job extends Serializable{ 


void execute(); 


} 

MethodInvokingJobDetailFactoryBean 


import org.apache.commons.logging.Log; 

import org.apache.commons.logging.LogFactory; 

import org.quartz.Job; 

import org.quartz.JobDetail; 

import org.quartz.JobExecutionContext; 

import org.quartz.JobExecutionException; 

import org.quartz.Scheduler; 

import org.quartz.StatefulJob; 

import org.springframework.beans.factory.BeanNameAware; 

import org.springframework.beans.factory.FactoryBean; 

import org.springframework.beans.factory.InitializingBean; 

import org.springframework.context.ApplicationContext; 

import org.springframework.util.MethodInvoker; 

public class MethodInvokingJobDetailFactoryBean implements FactoryBean, BeanNameAware, InitializingBean 

{ 

private Log logger = LogFactory.getLog(getClass()); 

private String group = Scheduler.DEFAULT_GROUP; 

private boolean concurrent = true; 

private boolean durable = false; 

private boolean volatility = false; 

private boolean shouldRecover = false; 

private String[] jobListenerNames; 

private String beanName; 

private JobDetail jobDetail; 

private String targetClass; 

private Object targetObject; 

private String targetMethod; 

private String staticMethod; 

private Object[] arguments; 

public String getTargetClass() 

{ 

return targetClass; 

} 

public void setTargetClass(String targetClass) 

{ 

this.targetClass = targetClass; 

} 

public String getTargetMethod() 

{ 

return targetMethod; 

} 

public void setTargetMethod(String targetMethod) 

{ 

this.targetMethod = targetMethod; 

} 

public Object getObject() throws Exception 

{ 

return jobDetail; 

} 

public Class getObjectType() 

{ 

return JobDetail.class; 

} 

public boolean isSingleton() 

{ 

return true; 

} 

public void setBeanName(String beanName) 

{ 

this.beanName = beanName; 

} 

public void afterPropertiesSet() throws Exception 

{ 

try 

{ 

logger.debug("start"); 


logger.debug("Creating JobDetail "+beanName); 

jobDetail = new JobDetail(); 

jobDetail.setName(beanName); 

jobDetail.setGroup(group); 

jobDetail.setJobClass(concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class); 

jobDetail.setDurability(durable); 

jobDetail.setVolatility(volatility); 

jobDetail.setRequestsRecovery(shouldRecover); 

if(targetClass!=null) 

jobDetail.getJobDataMap().put("targetClass", targetClass); 

if(targetObject!=null) 

jobDetail.getJobDataMap().put("targetObject", targetObject); 

if(targetMethod!=null) 

jobDetail.getJobDataMap().put("targetMethod", targetMethod); 

if(staticMethod!=null) 

jobDetail.getJobDataMap().put("staticMethod", staticMethod); 

if(arguments!=null) 

jobDetail.getJobDataMap().put("arguments", arguments); 


logger.debug("Registering JobListener names with JobDetail object "+beanName); 

if (this.jobListenerNames != null) { 

for (int i = 0; i < this.jobListenerNames.length; i++) { 

this.jobDetail.addJobListener(this.jobListenerNames[i]); 

} 

} 

logger.info("Created JobDetail: "+jobDetail+"; targetClass: "+targetClass+"; targetObject: "+targetObject+"; targetMethod: "+targetMethod+"; staticMethod: "+staticMethod+"; arguments: "+arguments+";"); 

} 

finally 

{ 

logger.debug("end"); 

} 

} 

public void setConcurrent(boolean concurrent) 

{ 

this.concurrent = concurrent; 

} 

public void setDurable(boolean durable) 

{ 

this.durable = durable; 

} 

public void setGroup(String group) 

{ 

this.group = group; 

} 

public void setJobListenerNames(String[] jobListenerNames) 

{ 

this.jobListenerNames = jobListenerNames; 

} 

public void setShouldRecover(boolean shouldRecover) 

{ 

this.shouldRecover = shouldRecover; 

} 

public void setVolatility(boolean volatility) 

{ 

this.volatility = volatility; 

} 

public static class MethodInvokingJob implements Job 

{ 

protected Log logger = LogFactory.getLog(getClass()); 

public void execute(JobExecutionContext context) throws JobExecutionException 

{ 

try 

{ 

logger.debug("start"); 

String targetClass = context.getMergedJobDataMap().getString("targetClass"); 

Class targetClassClass = null; 

if(targetClass!=null) 

{ 

targetClassClass = Class.forName(targetClass); // Could throw ClassNotFoundException 

} 

Object targetObject = context.getMergedJobDataMap().get("targetObject"); 

if(targetObject instanceof BootstrapJob){ 

ApplicationContext ac = (ApplicationContext)context.getScheduler().getContext().get("applicationContext"); 

BootstrapJob target = (BootstrapJob)targetObject ; 

target.execute(ac); 

}else{ 

String targetMethod = context.getMergedJobDataMap().getString("targetMethod"); 

String staticMethod = context.getMergedJobDataMap().getString("staticMethod"); 

Object[] arguments = (Object[])context.getMergedJobDataMap().get("arguments"); 

MethodInvoker methodInvoker = new MethodInvoker(); 

methodInvoker.setTargetClass(targetClassClass); 

methodInvoker.setTargetObject(targetObject); 

methodInvoker.setTargetMethod(targetMethod); 

methodInvoker.setStaticMethod(staticMethod); 

methodInvoker.setArguments(arguments); 

methodInvoker.prepare(); 

methodInvoker.invoke(); 

} 

} 

catch(Exception e) 

{ 

throw new JobExecutionException(e); 

} 

finally 

{ 

logger.debug("end"); 

} 

} 

}</pre> 

public static class StatefulMethodInvokingJob extends MethodInvokingJob implements StatefulJob 

{ 

// No additional functionality; just needs to implement StatefulJob. 

} 


public Object[] getArguments() 

{ 

return arguments; 

} 


public void setArguments(Object[] arguments) 

{ 

this.arguments = arguments; 

} 


public String getStaticMethod() 

{ 

return staticMethod; 

} 


public void setStaticMethod(String staticMethod) 

{ 

this.staticMethod = staticMethod; 

} 


public void setTargetObject(Object targetObject) 

{ 

this.targetObject = targetObject; 

} 

} 


实际的任务调度类:TaskDemo.java 

public class TaskDemo implements Job { 

public void execute(){ 

System.out.println("这个是实际的任务"); 

} 

} 

Spring applicationContext.xml 配置 


<bean name="taskJob" class="TaskDemo"/> 

 <bean id="_taskJob" class="PseudoJob"> 

<property name="targetJob" value="taskJob" /> 

</bean> 

<!--这里的MethodInvokingJobDetailFactoryBean路径要写自己重写的那个,非Spring包下的--> 

<bean id="taskJobDetail" class="MethodInvokingJobDetailFactoryBean"> 

<property name="concurrent" value="true" /> 

<property name="targetObject" ref="_taskJob" /> 

<property name="targetMethod" value="execute" /> 

</bean> 

 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> 

<property name="jobDetail"> 

<ref bean="taskJobDetail" /> 

</property> 

<property name="cronExpression"> 

<value>0 0 6 * * ?</value> 

</property> 

</bean> 

<!--dataSource采用spring中已有的dataSource--> 

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> 

<property name="configLocation" value="classpath:quartz.properties" /> 

<property name="dataSource" ref="dataSource" /> 

<property name="triggers"> 

<list> 

<ref bean="cronTrigger" /> 

</list> 

</property> 

 <!-- 这个属性决定当在spring配置文件里修改任务执行时间的时候,是否更新数据库,默认是不更新的,也就是说,你第一通过修改配置文件定义了时间,之后再修改是不会起任何作用的--> 

<property name="overwriteExistingJobs" value="true"/> 

<property name="applicationContextSchedulerContextKey" value="applicationContext" /> 

</bean>



OK,这样就大功告成了,别说还真神奇,经测试两台机器只有随机的一台执行定时任务.这篇文章只写了spring整合Quartz集群下的spring的配置,没有写Quartz的配置,有时间再整理一下.
Ps:好记性不如烂笔头儿,这技术的东西还是记载下来好.不过也真挺累啊.