Spring+Quartz的集群配置

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

解决办法: 一. 只在一台服务器发布带有定时器的程序,其他的全都去除定时任务再发布. (如果这台服务器宕掉了,那就悲剧了) ; 二. 采用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,并分享了出来.地址:http://download.csdn.net/source/2777549 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:好记性不如烂笔头儿,这技术的东西还是记载下来好.不过也真挺累啊.

转:Spring+Quartz的集群配置 | I'M .K + http://www.2016k.com/programmer/java/08-91.html

猜你喜欢

转载自kent-mu.iteye.com/blog/1846302