公司的一个将要上线的项目,采用了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();
}
}
}
具体的实你在附件中,只需修改数据库配置即可运行测试,如存在问题请留言或联系本人。
quartz+spring
猜你喜欢
转载自a335024800.iteye.com/blog/2290052
今日推荐
周排行