quartz+spring

公司的一个将要上线的项目,采用了quartz作为定时任务的解决方案,但是在生产环境中quartz的集群有问题,为了解决该问题,对quartz进行了一定时间的研究学习,通过网上的各种信息的综合,模拟出一个简单实用的集群配置实例。

    由于quartz集群基于数据库实现集群化方式,具体的数据库初始如下,该角本适用于oracle:

DROP TABLE  QRTZ_JOB_LISTENERS; 

DROP TABLE  QRTZ_TRIGGER_LISTENERS; 

DROP TABLE  QRTZ_FIRED_TRIGGERS; 

DROP TABLE  QRTZ_PAUSED_TRIGGER_GRPS; 

DROP TABLE  QRTZ_SCHEDULER_STATE; 

DROP TABLE  QRTZ_LOCKS; 

DROP TABLE QRTZ_SIMPLE_TRIGGERS; 

DROP TABLE  QRTZ_CRON_TRIGGERS; 

DROP TABLE  QRTZ_BLOB_TRIGGERS; 

DROP TABLE  QRTZ_TRIGGERS; 

DROP TABLE  QRTZ_JOB_DETAILS; 

DROP TABLE  QRTZ_CALENDARS; 

CREATE TABLE QRTZ_JOB_DETAILS 



JOB_NAME VARCHAR(200) NOT NULL, 

JOB_GROUP VARCHAR(200) NOT NULL, 

DESCRIPTION VARCHAR(250) NULL, 

JOB_CLASS_NAME VARCHAR(250) NOT NULL, 

IS_DURABLE VARCHAR(1) NOT NULL, 

IS_VOLATILE VARCHAR(1) NOT NULL, 

IS_STATEFUL VARCHAR(1) NOT NULL, 

REQUESTS_RECOVERY VARCHAR(1) NOT NULL, 

JOB_DATA BLOB NULL, 

PRIMARY KEY (JOB_NAME,JOB_GROUP) 

); 

 

CREATE TABLE QRTZ_JOB_LISTENERS 



JOB_NAME VARCHAR(200) NOT NULL, 

JOB_GROUP VARCHAR(200) NOT NULL, 

JOB_LISTENER VARCHAR(200) NOT NULL, 

PRIMARY KEY (JOB_NAME,JOB_GROUP,JOB_LISTENER), 

FOREIGN KEY (JOB_NAME,JOB_GROUP) 

REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP) 

); 

 

CREATE TABLE QRTZ_TRIGGERS 



TRIGGER_NAME VARCHAR(200) NOT NULL, 

TRIGGER_GROUP VARCHAR(200) NOT NULL, 

JOB_NAME VARCHAR(200) NOT NULL, 

JOB_GROUP VARCHAR(200) NOT NULL, 

IS_VOLATILE VARCHAR(1) NOT NULL, 

DESCRIPTION VARCHAR(250) NULL, 

NEXT_FIRE_TIME number(13) NULL, 

PREV_FIRE_TIME number(13) NULL, 

PRIORITY INTEGER NULL, 

TRIGGER_STATE VARCHAR(16) NOT NULL, 

TRIGGER_TYPE VARCHAR(8) NOT NULL, 

START_TIME number(13) NOT NULL, 

END_TIME number(13) NULL, 

CALENDAR_NAME VARCHAR(200) NULL, 

MISFIRE_INSTR number(2) NULL, 

JOB_DATA BLOB NULL, 

PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), 

FOREIGN KEY (JOB_NAME,JOB_GROUP) 

REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP) 

); 

 

CREATE TABLE QRTZ_SIMPLE_TRIGGERS 



TRIGGER_NAME VARCHAR(200) NOT NULL, 

TRIGGER_GROUP VARCHAR(200) NOT NULL, 

REPEAT_COUNT number(7) NOT NULL, 

REPEAT_INTERVAL number(12) NOT NULL, 

TIMES_TRIGGERED number(7) NOT NULL, 

PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), 

FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) 

REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) 

); 

 

CREATE TABLE QRTZ_CRON_TRIGGERS 



TRIGGER_NAME VARCHAR(200) NOT NULL, 

TRIGGER_GROUP VARCHAR(200) NOT NULL, 

CRON_EXPRESSION VARCHAR(200) NOT NULL, 

TIME_ZONE_ID VARCHAR(80), 

PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), 

FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) 

REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) 

); 

 

CREATE TABLE QRTZ_BLOB_TRIGGERS 



TRIGGER_NAME VARCHAR(200) NOT NULL, 

TRIGGER_GROUP VARCHAR(200) NOT NULL, 

BLOB_DATA BLOB NULL, 

PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), 

FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) 

REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) 

); 

 

CREATE TABLE QRTZ_TRIGGER_LISTENERS 



TRIGGER_NAME VARCHAR(200) NOT NULL, 

TRIGGER_GROUP VARCHAR(200) NOT NULL, 

TRIGGER_LISTENER VARCHAR(200) NOT NULL, 

PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_LISTENER), 

FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) 

REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) 

); 

 

 

CREATE TABLE QRTZ_CALENDARS 



CALENDAR_NAME VARCHAR(200) NOT NULL, 

CALENDAR BLOB NOT NULL, 

PRIMARY KEY (CALENDAR_NAME) 

); 

 

 

 

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS 



TRIGGER_GROUP VARCHAR(200) NOT NULL, 

PRIMARY KEY (TRIGGER_GROUP) 

); 

 

CREATE TABLE QRTZ_FIRED_TRIGGERS 



ENTRY_ID VARCHAR(95) NOT NULL, 

TRIGGER_NAME VARCHAR(200) NOT NULL, 

TRIGGER_GROUP VARCHAR(200) NOT NULL, 

IS_VOLATILE VARCHAR(1) NOT NULL, 

INSTANCE_NAME VARCHAR(200) NOT NULL, 

FIRED_TIME number(13) NOT NULL, 

PRIORITY INTEGER NOT NULL, 

STATE VARCHAR(16) NOT NULL, 

JOB_NAME VARCHAR(200) NULL, 

JOB_GROUP VARCHAR(200) NULL, 

IS_STATEFUL VARCHAR(1) NULL, 

REQUESTS_RECOVERY VARCHAR(1) NULL, 

PRIMARY KEY (ENTRY_ID) 

); 

 

CREATE TABLE QRTZ_SCHEDULER_STATE 



INSTANCE_NAME VARCHAR(200) NOT NULL, 

LAST_CHECKIN_TIME number(13) NOT NULL, 

CHECKIN_INTERVAL number(13) NOT NULL, 

PRIMARY KEY (INSTANCE_NAME) 

); 

 

CREATE TABLE QRTZ_LOCKS 



LOCK_NAME VARCHAR(40) NOT NULL, 

PRIMARY KEY (LOCK_NAME) 

); 

INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS'); 

INSERT INTO QRTZ_LOCKS values('JOB_ACCESS'); 

INSERT INTO QRTZ_LOCKS values('CALENDAR_ACCESS'); 

INSERT INTO QRTZ_LOCKS values('STATE_ACCESS'); 

INSERT INTO QRTZ_LOCKS values('MISFIRE_ACCESS'); 

commit;  

对于quartz集群具体实现通过加载bean的配置信息,将quartz.properties信息加载到应用中,根据xml与properties中的配置信息进行集群的实现,quartz.properties详细配置如下所示:

org.quartz.scheduler.instanceName = scheduler

org.quartz.scheduler.instanceId = AUTO

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool 

org.quartz.threadPool.threadCount = 25 

org.quartz.threadPool.threadPriority = 5 

org.quartz.jobStore.misfireThreshold = 60000 

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate 

org.quartz.jobStore.useProperties = false 

org.quartz.jobStore.dataSource = myDS 

org.quartz.jobStore.tablePrefix = QRTZ_ 

org.quartz.jobStore.isClustered = true 

org.quartz.jobStore.clusterCheckinInterval = 20000 

org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver 

org.quartz.dataSource.myDS.URL = jdbc\:oracle\:thin\:@127.0.0.1\:1521\:orcl

org.quartz.dataSource.myDS.user = zsp 

org.quartz.dataSource.myDS.password = orcl 

org.quartz.dataSource.myDS.maxConnections = 5 

org.quartz.dataSource.myDS.validationQuery=select 0 from dual

该配置文件中的instanceName 应保持一致,集群会根据该实例名进行统一的分发检查,实现quartz的功能,最后通过application-quartz.xml中的各项配置完成信息的整合,具体配置信息如下:


数据库的配置信息如下application.xml:



由于spring的类中存在 bug需要使用独立的类文件,具体类文件如下:

package frameworkx.springframework.scheduling.quartz;


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.BeansException;

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.context.ApplicationContextAware;

import org.springframework.util.MethodInvoker;


/**

* This is a cluster safe Quartz/Spring FactoryBean implementation, which

* produces a JobDetail implementation that can invoke any no-arg method on any

* bean deployed within a Spring container.

*

* Use this Class instead of the MethodInvokingJobDetailBeanFactory Class

* provided by Spring when deploying to a web environment like Tomcat.

*

* Implementation

* The Spring ApplicationContext cannot be passed to a Job via the JobDataMap,

* because it is not Serializable (and for very good reason!) So, instead of

* associating an ApplicationContext with a JobDetail or a Trigger object, I

* made the [Stateful]BeanInvokingJob, which is not persisted in the database,

* get the applicationContext from the BeanInvokingJobDetailFactoryBean, which

* is ApplicationContextAware, when the [Stateful]BeanInvokingJob is created and

* executed.

*

* The name or id of the of the bean to invoke (targetBean) and the method to

* invoke (targetMethod) must be provided in the bean declaration or a

* JobExecutionException will be thrown.

*

* I wrote BeanInvokingJobDetailFactoryBean, because the

* MethodInvokingJobDetailFactoryBean does not produce Serializable JobDetail

* objects, and as a result cannot be deployed into a clustered environment (as

* is documented within the Class).

*

* Example

*

* <bean id="exampleBean" class="example.ExampleImpl">

</bean>



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

* <!-- Execute exampleBean.fooBar() at 2am every day -->

<property name="cronExpression" value="0 0 2 * * ?" />

<property name="jobDetail">



<bean class="frameworkx.springframework.scheduling.quartz.BeanInvokingJobDetailFactoryBean">



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

<property name="targetBean" value="exampleBean" />

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

<property name="arguments">



<list>



<value>arg1Value</value>

<value>arg2Value</value>



<list>



</property>



</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 BeanInvokingJobDetailFactoryBean, which

* will produce a JobDetail Object with the jobClass property set to

* StatefulBeanInvokingJob.class (concurrent=="false"; Set to

* BeanInvokingJob.class when concurrent=="true"), which will in turn invoke the

* fooBar(String, String) method of the bean with id "

* exampleBean". Method arguments are optional. In

* this case there are two String arguments being provided to the

* fooBar method. The Scheduler is the heart of the whole

* operation; without it, nothing will happen.

*

* For more information on cronExpression visit http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html

*

*

* Troubleshooting

*

* Error: java.io.IOException: JobDataMap values must be Strings when the

* 'useProperties' property is set. Key of offending value: arguments

* Solution: do not set the arguments property when

* org.quartz.jobstore.useProperty is set to "true" in

* quartz.properties.

*

*

* @author Stephen M. Wick

*

* @see #afterPropertiesSet()

*/

public class BeanInvokingJobDetailFactoryBean implements FactoryBean, BeanNameAware, InitializingBean, ApplicationContextAware {

/**

* Set by setApplicationContext when a

* BeanInvokingJobDetailFactoryBean is defined within a Spring

* ApplicationContext as a bean.

*

* Used by the execute method of the BeanInvokingJob and

* StatefulBeanInvokingJob classes.

*

* @see #setApplicationContext(ApplicationContext)

* @see BeanInvokingJob#execute(JobExecutionContext)

*/

protected static ApplicationContext applicationContext;


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()

* @see Scheduler#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

* BeanInvokingJob.class or StatefulBeanInvokingJob.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.

*

* @see http://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.

*

* @see http://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.

*

* @see http://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()

**/

private String[] jobListenerNames;


/**

* The name assigned to this bean in the Spring ApplicationContext. Used by

* afterPropertiesSet to set the JobDetail.name property.

*

* @see afterPropertiesSet()

* @see JobDetail#setName(String)

**/

private String beanName;


/**

* The JobDetail produced by the afterPropertiesSet method, and

* returned by the getObject method of the Spring FactoryBean

* interface.

*

* @see #afterPropertiesSet()

* @see #getObject()

* @see FactoryBean

**/

private JobDetail jobDetail;


/**

* The name or id of the bean to invoke, as it is declared in the Spring

* ApplicationContext.

**/

private String targetBean;


/**

* The method to invoke on the bean identified by the targetBean property.

**/

private String targetMethod;


/**

* The arguments to provide to the method identified by the targetMethod

* property. These Objects must be Serializable when concurrent=="true".

*/

private Object[] arguments;


/**

* Get the targetBean property.

*

* @see #targetBean

* @return targetBean

*/

public String getTargetBean() {

return targetBean;

}


/**

* Set the targetBean property.

*

* @see #targetBean

*/

public void setTargetBean(String targetBean) {

this.targetBean = targetBean;

}


/**

* Get the targetMethod property.

*

* @see #targetMethod

* @return targetMethod

*/

public String getTargetMethod() {

return targetMethod;

}


/**

* Set the targetMethod property.

*

* @see #targetMethod

*/

public void setTargetMethod(String targetMethod) {

this.targetMethod = targetMethod;

}


/**

* @return jobDetail - The JobDetail that is created by the

*         afterPropertiesSet method of this FactoryBean

* @see #jobDetail

* @see #afterPropertiesSet()

* @see FactoryBean#getObject()

*/

public Object getObject() throws Exception {

return jobDetail;

}


/**

* @return JobDetail.class

* @see FactoryBean#getObjectType()

*/

public Class getObjectType() {

return JobDetail.class;

}


/**

* @return true

* @see FactoryBean#isSingleton()

*/

public boolean isSingleton() {

return true;

}


/**

* Set the beanName property.

*

* @see #beanName

* @see BeanNameAware#setBeanName(String)

*/

public void setBeanName(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 to beanName

* jobDetail.group is set to group

* jobDetail.jobClass is set to BeanInvokingJob.class or

* StatefulBeanInvokingJob.class depending on whether the

* concurrent property is set to true or false, respectively.

*

* jobDetail.durability is set to durable

* jobDetail.volatility is set to volatility

* jobDetail.requestsRecovery is set to shouldRecover

* jobDetail.jobDataMap["targetBean"] is set to targetBean

* jobDetail.jobDataMap["targetMethod"] is set to

* targetMethod

* jobDetail.jobDataMap["arguments"] is set to arguments

* Each JobListener name in jobListenerNames 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}; targetBean:

* {targetBean}; targetMethod: {targetMethod};

* arguments: {arguments};

* DEBUG: end

*

*

* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()

* @see JobDetail

* @see #jobDetail

* @see #beanName

* @see #group

* @see BeanInvokingJob

* @see StatefulBeanInvokingJob

* @see #durable

* @see #volatility

* @see #shouldRecover

* @see #targetBean

* @see #targetMethod

* @see #arguments

* @see #jobListenerNames

*/

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 ? BeanInvokingJob.class : StatefulBeanInvokingJob.class);

jobDetail.setDurability(durable);

jobDetail.setVolatility(volatility);

jobDetail.setRequestsRecovery(shouldRecover);

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

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

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


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

if (this.jobListenerNames != null) {

for (int i = 0; i

* Invoked by Spring as a result of the ApplicationContextAware interface

* implemented by this Class.

*

* @see ApplicationContextAware#setApplicationContext(ApplicationContext)

*/

public void setApplicationContext(ApplicationContext context) throws BeansException {

applicationContext = context;

}


public void setArguments(Object[] arguments) {

this.arguments = arguments;

}


/**

* 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 "targetBean" and

* "targetMethod".

* - It uses the value of the targetBean 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 targetBean) to invoke.

*

* It uses the static ApplicationContext in the

* BeanInvokingJobDetailFactoryBean, 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.

*

* @see BeanInvokingJobDetailFactoryBean#applicationContext

* @see #execute(JobExecutionContext)

*

* @author Stephen M. Wick

*/

public static class BeanInvokingJob implements Job {

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


/**

* When invoked by a Quartz scheduler, execute invokes a

* method on a bean deployed within the scheduler's Spring

* ApplicationContext.

*

* Implementation

* The bean is identified by the "targetBean" entry in the JobDataMap of

* the JobExecutionContext provided.

* The method is identified by the "targetMethod" entry in the

* JobDataMap of the JobExecutionContext provided.

*

* The Quartz scheduler shouldn't start up correctly if the bean

* identified by "targetBean" cannot be found in the scheduler's Spring

* ApplicationContext. BeanFactory.getBean() throws an exception if the

* targetBean doesn't exist, so I'm not going to waste any code testing

* for the bean's existance in the ApplicationContext.

*

* Logging is provided at the DEBUG and INFO levels; 5 lines at the

* DEBUG level, and 1 line at the INFO level.

*

* @see Job#execute(JobExecutionContext)

*/

public void execute(JobExecutionContext context) throws JobExecutionException {

try {

logger.debug("start");


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

logger.debug("targetBean is " + targetBean);

if (targetBean == null)

throw new JobExecutionException("targetBean cannot be null.", false);


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

logger.debug("targetMethod is " + targetMethod);

if (targetMethod == null)

throw new JobExecutionException("targetMethod cannot be null.", false);


// when org.quartz.jobStore.useProperties=="true" the arguments

// entry (which should be an Object[]) in the JobDataMap gets

// converted into a String.

Object argumentsObject = context.getMergedJobDataMap().get("arguments");

Object[] arguments = (argumentsObject instanceof String) ? null : (Object[]) argumentsObject;

logger.debug("arguments array is " + arguments);


Object bean = applicationContext.getBean(targetBean);

logger.debug("applicationContext resolved bean name/id '" + targetBean + "' to " + bean);


MethodInvoker beanMethod = new MethodInvoker();

beanMethod.setTargetObject(bean);

beanMethod.setTargetMethod(targetMethod);

beanMethod.setArguments(arguments);

beanMethod.prepare();

logger.info("Invoking Bean: " + targetBean + "; Method: " + targetMethod + "; arguments: " + arguments + ";");

beanMethod.invoke();

} catch (JobExecutionException e) {

throw e;

} catch (Exception e) {

throw new JobExecutionException(e);

} finally {

logger.debug("end");

}

}

}


public static class StatefulBeanInvokingJob extends BeanInvokingJob implements StatefulJob {

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

}

}

文件2:

package frameworkx.springframework.scheduling.quartz;


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.util.MethodInvoker;


/**

* 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

*

* @author Stephen M. Wick

*

* @see #afterPropertiesSet()

*/

public class MethodInvokingJobDetailFactoryBean implements FactoryBean, 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()

* @see Scheduler#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.

*

* @see http://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.

*

* @see http://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.

*

* @see http://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()

**/

private String[] jobListenerNames;


/**

* The name assigned to this bean in the Spring ApplicationContext. Used by

* afterPropertiesSet to set the JobDetail.name property.

*

* @see afterPropertiesSet()

* @see JobDetail#setName(String)

**/

private String beanName;


/**

* The JobDetail produced by the afterPropertiesSet method, and

* returned by the getObject method of the Spring FactoryBean

* interface.

*

* @see #afterPropertiesSet()

* @see #getObject()

* @see FactoryBean

**/

private JobDetail jobDetail;


/**

* The name of the Class to invoke.

**/

private String 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.

*/

private Object 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.

**/

private String 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.

*/

private String 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.

*/

private Object[] arguments;


/**

* Get the targetClass property.

*

* @see #targetClass

* @return targetClass

*/

public String getTargetClass() {

return targetClass;

}


/**

* Set the targetClass property.

*

* @see #targetClass

*/

public void setTargetClass(String targetClass) {

this.targetClass = targetClass;

}


/**

* Get the targetMethod property.

*

* @see #targetMethod

* @return targetMethod

*/

public String getTargetMethod() {

return targetMethod;

}


/**

* Set the targetMethod property.

*

* @see #targetMethod

*/

public void setTargetMethod(String targetMethod) {

this.targetMethod = targetMethod;

}


/**

* @return jobDetail - The JobDetail that is created by the

*         afterPropertiesSet method of this FactoryBean

* @see #jobDetail

* @see #afterPropertiesSet()

* @see FactoryBean#getObject()

*/

public Object getObject() throws Exception {

return jobDetail;

}


/**

* @return JobDetail.class

* @see FactoryBean#getObjectType()

*/

public Class getObjectType() {

return JobDetail.class;

}


/**

* @return true

* @see FactoryBean#isSingleton()

*/

public boolean isSingleton() {

return true;

}


/**

* Set the beanName property.

*

* @see #beanName

* @see BeanNameAware#setBeanName(String)

*/

public void setBeanName(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 to beanName

* jobDetail.group is set to group

* jobDetail.jobClass is set to MethodInvokingJob.class or

* StatefulMethodInvokingJob.class depending on whether the

* concurrent property is set to true or false, respectively.

*

* jobDetail.durability is set to durable

* jobDetail.volatility is set to volatility

* jobDetail.requestsRecovery is set to shouldRecover

* jobDetail.jobDataMap["targetClass"] is set to

* targetClass

* jobDetail.jobDataMap["targetMethod"] is set to

* targetMethod

* Each JobListener name in jobListenerNames 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

*

*

* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()

* @see JobDetail

* @see #jobDetail

* @see #beanName

* @see #group

* @see MethodInvokingJob

* @see StatefulMethodInvokingJob

* @see #durable

* @see #volatility

* @see #shouldRecover

* @see #targetClass

* @see #targetMethod

* @see #jobListenerNames

*/

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

* 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.

*

* @see MethodInvokingJobDetailFactoryBean#applicationContext

* @see #execute(JobExecutionContext)

*

* @author Stephen M. Wick

*/

public static class MethodInvokingJob implements Job {

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.

*

* @see Job#execute(JobExecutionContext)

*/

public void execute(JobExecutionContext context) throws JobExecutionException {

try {

logger.debug("��ʼ");

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("������ is " + targetObject);

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

logger.debug("���� is " + targetMethod);

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

logger.debug("staticMethod is " + staticMethod);

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

logger.debug("���� " + arguments);


logger.debug("�������� MethodInvoker");

MethodInvoker methodInvoker = new MethodInvoker();

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) {

logger.error("execute ",e);

throw new JobExecutionException(e);

} finally {

logger.debug("end");

}

}

}


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;

}

}

整合完成后,在web项目中只要配置了spring启动文件即可自动触发定时任务,在单个 main方法中使用如下方式:

public class Client {

public static void main(String[] args) {

ApplicationContext context = new ClassPathXmlApplicationContext(

new String[]{"classpath:/applicationContext.xml","classpath:/applicationContext-quartz.xml"});

Scheduler scheduler = (Scheduler) context.getBean("scheduler");

try {

scheduler.start();

} catch (SchedulerException e) {

e.printStackTrace();

}

}

}

具体的实你在附件中,只需修改数据库配置即可运行测试,如存在问题请留言或联系本人。

猜你喜欢

转载自a335024800.iteye.com/blog/2290052