packageking.springframework.scheduling.quartz;importorg.apache.commons.logging.Log;importorg.apache.commons.logging.LogFactory;importorg.quartz.Job;importorg.quartz.JobDetail;importorg.quartz.JobExecutionContext;importorg.quartz.JobExecutionException;importorg.quartz.Scheduler;importorg.quartz.StatefulJob;importorg.springframework.beans.factory.BeanNameAware;importorg.springframework.beans.factory.FactoryBean;importorg.springframework.beans.factory.InitializingBean;importorg.springframework.context.ApplicationContext;importorg.springframework.util.MethodInvoker;/*** 该类可让生成的job可以序列化到数据库中,Spring自带的MethodInvokingJobDetailFactoryBean却不能序列化生成的job
* This is a cluster safe Quartz/Spring FactoryBean implementation, which produces a JobDetail implementation that can invoke any no-arg method on any Class.
*
* Use this Class instead of the MethodInvokingJobDetailBeanFactory Class provided by Spring when deploying to a web environment like Tomcat.
*
* Implementation
* Instead of associating a MethodInvoker with a JobDetail or a Trigger object, like Spring's MethodInvokingJobDetailFactoryBean does, I made the [Stateful]MethodInvokingJob, which is not persisted in the database, create the MethodInvoker when the [Stateful]MethodInvokingJob is created and executed.
*
* A method can be invoked one of several ways:
*
*
The name of the Class to invoke (targetClass) and the static method to invoke (targetMethod) can be specified.*
The Object to invoke (targetObject) and the static or instance method to invoke (targetMethod) can be specified (the targetObject must be Serializable when concurrent=="false").*
The Class and static Method to invoke can be specified in one property (staticMethod). example: staticMethod = "example.ExampleClass.someStaticMethod"* Note: An Object[] of method arguments can be specified (arguments), but the Objects must be Serializable if concurrent=="false".
*
*
* I wrote MethodInvokingJobDetailFactoryBean, because Spring's MethodInvokingJobDetailFactoryBean does not produce Serializable
* JobDetail objects, and as a result cannot be deployed into a clustered environment like Tomcat (as is documented within the Class).
*
* Example
*
*
* <bean id="exampleTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
*
<!-- Execute example.ExampleImpl.fooBar() at 2am every day -->
<property name="cronExpression" value="0 0 2 * * ?" />
<property name="jobDetail">
<bean class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="concurrent" value="false"/>
<property name="targetClass" value="example.ExampleImpl" />
<property name="targetMethod" value="fooBar" />
</bean>
</property>
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="exampleTrigger" />
</list>
</property>
</bean>
*
* In this example we created a MethodInvokingJobDetailFactoryBean, which will produce a JobDetail Object with the jobClass property set to StatefulMethodInvokingJob.class (concurrent=="false"; Set to MethodInvokingJob.class when concurrent=="true"), which will in turn invoke the static fooBar
() method of the "example.ExampleImpl
" Class. The Scheduler is the heart of the whole operation; without it, nothing will happen.
*
* For more information on cronExpression
syntax visit http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html
*
*@authorStephen M. Wick
*
*@see#afterPropertiesSet()*/
public class MethodInvokingJobDetailFactoryBean implementsFactoryBean, BeanNameAware, InitializingBean
{private Log logger =LogFactory.getLog(getClass());/*** The JobDetail produced by the afterPropertiesSet
method of this Class will be assigned to the Group specified by this property. Default: Scheduler.DEFAULT_GROUP
*@see#afterPropertiesSet()
*@seeScheduler#DEFAULT_GROUP*/
private String group =Scheduler.DEFAULT_GROUP;/*** Indicates whether or not the Bean Method should be invoked by more than one Scheduler at the specified time (like when deployed to a cluster, and/or when there are multiple Spring ApplicationContexts in a single JVM - Tomcat 5.5 creates 2 or more instances of the DispatcherServlet (a pool), which in turn creates a separate Spring ApplicationContext for each instance of the servlet)
*
* Used by afterPropertiesSet
to set the JobDetail.jobClass to MethodInvokingJob.class or StatefulMethodInvokingJob.class when true or false, respectively. Default: true
*@see#afterPropertiesSet()*/
private boolean concurrent = true;/**Used to set the JobDetail.durable property. Default: false
*
Durability - if a job is non-durable, it is automatically deleted from the scheduler once there are no longer any active triggers associated with it.
*@seehttp://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html
*@see#afterPropertiesSet()*/
private boolean durable = false;/*** Used by afterPropertiesSet
to set the JobDetail.volatile property. Default: false
*
Volatility - if a job is volatile, it is not persisted between re-starts of the Quartz scheduler.
*
I set the default to false to be the same as the default for a Quartz Trigger. An exception is thrown
* when the Trigger is non-volatile and the Job is volatile. If you want volatility, then you must set this property, and the Trigger's volatility property, to true.
*@seehttp://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html
*@see#afterPropertiesSet()*/
private boolean volatility = false;/*** Used by afterPropertiesSet
to set the JobDetail.requestsRecovery property. Default: false
*
RequestsRecovery - if a job "requests recovery", and it is executing during the time of a 'hard shutdown' of the scheduler (i.e. the process it is running within crashes, or the machine is shut off), then it is re-executed when the scheduler is started again. In this case, the JobExecutionContext.isRecovering() method will return true.
*@seehttp://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html
*@see#afterPropertiesSet()*/
private boolean shouldRecover = false;/*** A list of names of JobListeners to associate with the JobDetail object created by this FactoryBean.
*
*@see#afterPropertiesSet()
**/
privateString[] jobListenerNames;/**The name assigned to this bean in the Spring ApplicationContext.
* Used by afterPropertiesSet
to set the JobDetail.name property.
*@seeafterPropertiesSet()
*@seeJobDetail#setName(String)
**/
privateString beanName;/*** The JobDetail produced by the afterPropertiesSet
method, and returned by the getObject
method of the Spring FactoryBean interface.
*@see#afterPropertiesSet()
*@see#getObject()
*@seeFactoryBean
**/
privateJobDetail jobDetail;/*** The name of the Class to invoke.
**/
privateString targetClass;/*** The Object to invoke.
*
* {@link#targetClass} or targetObject must be set, but not both.
*
* This object must be Serializable when {@link#concurrent} is set to false.*/
privateObject targetObject;/*** The instance method to invoke on the Class or Object identified by the targetClass or targetObject property, respectfully.
*
* targetMethod or {@link#staticMethod} should be set, but not both.
**/
privateString targetMethod;/*** The static method to invoke on the Class or Object identified by the targetClass or targetObject property, respectfully.
*
* {@link#targetMethod} or staticMethod should be set, but not both.*/
privateString staticMethod;/*** Method arguments provided to the {@link#targetMethod} or {@link#staticMethod} specified.
*
* All arguments must be Serializable when {@link#concurrent} is set to false.
*
* I strongly urge you not to provide arguments until Quartz 1.6.1 has been released if you are using a JDBCJobStore with
* Microsoft SQL Server. There is a bug in version 1.6.0 that prevents Quartz from Serializing the Objects in the JobDataMap
* to the database. The workaround is to set the property "org.opensymphony.quaryz.useProperties = true" in your quartz.properties file,
* which tells Quartz not to serialize Objects in the JobDataMap, but to instead expect all String compliant values.*/
privateObject[] arguments;/*** Get the targetClass property.
*@see#targetClass
*@returntargetClass*/
publicString getTargetClass()
{returntargetClass;
}/*** Set the targetClass property.
*@see#targetClass*/
public voidsetTargetClass(String targetClass)
{this.targetClass =targetClass;
}/*** Get the targetMethod property.
*@see#targetMethod
*@returntargetMethod*/
publicString getTargetMethod()
{returntargetMethod;
}/*** Set the targetMethod property.
*@see#targetMethod*/
public voidsetTargetMethod(String targetMethod)
{this.targetMethod =targetMethod;
}/***@returnjobDetail - The JobDetail that is created by the afterPropertiesSet method of this FactoryBean
*@see#jobDetail
*@see#afterPropertiesSet()
*@seeFactoryBean#getObject()*/
public Object getObject() throwsException
{returnjobDetail;
}/***@returnJobDetail.class
*@seeFactoryBean#getObjectType()*/
publicClass getObjectType()
{return JobDetail.class;
}/***@returntrue
*@seeFactoryBean#isSingleton()*/
public booleanisSingleton()
{return true;
}/*** Set the beanName property.
*@see#beanName
*@seeBeanNameAware#setBeanName(String)*/
public voidsetBeanName(String beanName)
{this.beanName =beanName;
}/*** Invoked by the Spring container after all properties have been set.
*
* Sets the jobDetail
property to a new instance of JobDetail
*
*
jobDetail.name is set tobeanName
*
jobDetail.group is set togroup
*
jobDetail.jobClass is set to MethodInvokingJob.class or StatefulMethodInvokingJob.class depending on whether theconcurrent
property is set to true or false, respectively.
*
jobDetail.durability is set todurable
*
jobDetail.volatility is set tovolatility
*
jobDetail.requestsRecovery is set toshouldRecover
*
jobDetail.jobDataMap["targetClass"] is set totargetClass
*
jobDetail.jobDataMap["targetMethod"] is set totargetMethod
*
Each JobListener name injobListenerNames
is added to the
jobDetail
object.
*
*
* Logging occurs at the DEBUG and INFO levels; 4 lines at the DEBUG level, and 1 line at the INFO level.
*
*
DEBUG: start*
DEBUG: Creating JobDetail{beanName}
*
DEBUG: Registering JobListener names with JobDetail object{beanName}
*
INFO: Created JobDetail:{jobDetail}
; targetClass:
{targetClass}
; targetMethod:
{targetMethod}
;
*
DEBUG: end*
*@seeorg.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*@seeJobDetail
*@see#jobDetail
*@see#beanName
*@see#group
*@seeMethodInvokingJob
*@seeStatefulMethodInvokingJob
*@see#durable
*@see#volatility
*@see#shouldRecover
*@see#targetClass
*@see#targetMethod
*@see#jobListenerNames*/
public void afterPropertiesSet() throwsException
{try{
logger.debug("start");
logger.debug("Creating JobDetail "+beanName);
jobDetail= newJobDetail();
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");
}
}/*** Setter for the concurrent property.
*
*@paramconcurrent
*@see#concurrent*/
public void setConcurrent(booleanconcurrent)
{this.concurrent =concurrent;
}/*** setter for the durable property.
*
*@paramdurable
*
*@see#durable*/
public void setDurable(booleandurable)
{this.durable =durable;
}/*** setter for the group property.
*
*@paramgroup
*
*@see#group*/
public voidsetGroup(String group)
{this.group =group;
}/*** setter for the {@link#jobListenerNames} property.
*
*@paramjobListenerNames
*@see#jobListenerNames*/
public voidsetJobListenerNames(String[] jobListenerNames)
{this.jobListenerNames =jobListenerNames;
}/*** setter for the {@link#shouldRecover} property.
*
*@paramshouldRecover
*@see#shouldRecover*/
public void setShouldRecover(booleanshouldRecover)
{this.shouldRecover =shouldRecover;
}/*** setter for the {@link#volatility} property.
*
*@paramvolatility
*@see#volatility*/
public void setVolatility(booleanvolatility)
{this.volatility =volatility;
}/*** This is a cluster safe Job designed to invoke a method on any bean defined within the same Spring
* ApplicationContext.
*
* The only entries this Job expects in the JobDataMap are "targetClass" and "targetMethod".
* - It uses the value of the targetClass
entry to get the desired bean from the Spring ApplicationContext.
* - It uses the value of the targetMethod
entry to determine which method of the Bean (identified by targetClass) to invoke.
*
* It uses the static ApplicationContext in the MethodInvokingJobDetailFactoryBean,
* which is ApplicationContextAware, to get the Bean with which to invoke the method.
*
* All Exceptions thrown from the execute method are caught and wrapped in a JobExecutionException.
*
*@seeMethodInvokingJobDetailFactoryBean#applicationContext
*@see#execute(JobExecutionContext)
*
*@authorStephen M. Wick*/
public static class MethodInvokingJob implementsJob
{protected Log logger =LogFactory.getLog(getClass());/*** When invoked by a Quartz scheduler, execute
invokes a method on a Class or Object in the JobExecutionContext provided.
*
* Implementation
* The Class is identified by the "targetClass" entry in the JobDataMap of the JobExecutionContext provided. If targetClass is specified, then targetMethod must be a static method.
* The Object is identified by the 'targetObject" entry in the JobDataMap of the JobExecutionContext provided. If targetObject is provided, then targetClass will be overwritten. This Object must be Serializable when concurrent
is set to false.
* The method is identified by the "targetMethod" entry in the JobDataMap of the JobExecutionContext provided.
* The "staticMethod" entry in the JobDataMap of the JobExecutionContext can be used to specify a Class and Method in one entry (ie: "example.ExampleClass.someStaticMethod")
* The method arguments (an array of Objects) are identified by the "arguments" entry in the JobDataMap of the JobExecutionContext. All arguments must be Serializable when concurrent
is set to false.
*
* Logging is provided at the DEBUG and INFO levels; 8 lines at the DEBUG level, and 1 line at the INFO level.
*@seeJob#execute(JobExecutionContext)*/
public void execute(JobExecutionContext context) throwsJobExecutionException
{try{
logger.debug("start");
String targetClass= context.getMergedJobDataMap().getString("targetClass");
logger.debug("targetClass is "+targetClass);
Class targetClassClass= null;if(targetClass!=null)
{
targetClassClass= Class.forName(targetClass); //Could throw ClassNotFoundException
}
Object targetObject= context.getMergedJobDataMap().get("targetObject");
logger.debug("targetObject is "+targetObject);
String targetMethod= context.getMergedJobDataMap().getString("targetMethod");
logger.debug("targetMethod is "+targetMethod);
String staticMethod= context.getMergedJobDataMap().getString("staticMethod");
logger.debug("staticMethod is "+staticMethod);
Object[] arguments= (Object[])context.getMergedJobDataMap().get("arguments");
logger.debug("arguments are "+arguments);
logger.debug("creating MethodInvoker");
MethodInvoker methodInvoker= newMethodInvoker();
methodInvoker.setTargetClass(targetClassClass);
methodInvoker.setTargetObject(targetObject);
methodInvoker.setTargetMethod(targetMethod);
methodInvoker.setStaticMethod(staticMethod);
methodInvoker.setArguments(arguments);
methodInvoker.prepare();
logger.info("Invoking: "+methodInvoker.getPreparedMethod().toGenericString());
methodInvoker.invoke();
}catch(Exception e)
{throw newJobExecutionException(e);
}finally{
logger.debug("end");
}
}
}public static class StatefulMethodInvokingJob extends MethodInvokingJob implementsStatefulJob
{//No additional functionality; just needs to implement StatefulJob.
}publicObject[] getArguments()
{returnarguments;
}public voidsetArguments(Object[] arguments)
{this.arguments =arguments;
}publicString getStaticMethod()
{returnstaticMethod;
}public voidsetStaticMethod(String staticMethod)
{this.staticMethod =staticMethod;
}public voidsetTargetObject(Object targetObject)
{this.targetObject =targetObject;
}
}