使用的jar包:spring2.5.六、quartz1.8.6 html
由於spring內置的quartz版本變化,因此存在spring和quartz版本接口兼容狀況,以下: java
1)spring3.1以後的版本(包含)兼容quartz2.X(這裏有個坑,網絡資料是spring3.0對應quartz1.X,但也有人說支持quartz2.X,只要不在spring的配置文件中使用,直接在程序中調用就能夠(廢話),具體須要驗證) mysql
2)spring3.1以前的版本兼容quartz1.X web
Demo程序目錄結構: spring
Demo核心程序源碼:sql
package com.alfred.java.quartz.distschedule.springdemo01; import java.text.SimpleDateFormat; import java.util.Date; import org.quartz.JobExecutionException; public class JobHello { private static String NUM = "1"; private static Integer count = 1; private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); protected void executeInternal() throws JobExecutionException { System.out.println("job hello"+NUM+":"+(count++)+":"+sdf.format(new Date())); } public static void setNUM(String nUM) { NUM = nUM; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置數據源 --> <bean id="quartzDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>org.gjt.mm.mysql.Driver</value> </property> <property name="url"> <value>jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8</value> </property> <property name="username"> <value>root</value> </property> <property name="password"> <value>123456</value> </property> <property name="maxActive" value="5"></property> <property name="maxIdle" value="20"></property> <property name="maxWait" value="50"></property> <property name="defaultAutoCommit" value="true"></property> </bean> <!-- 配置job --> <bean id="jobHelloDetail" class="com.alfred.java.quartz.distschedule.springdemo01.JobHello"> </bean> <bean id="jobHelloTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail"> <bean class="frameworkx.springframework.scheduling.quartz.BeanInvokingJobDetailFactoryBean"> <property name="concurrent" value="false"/> <property name="targetBean" value="jobHelloDetail" /> <property name="targetMethod" value="executeInternal" /> </bean> </property> <property name="startDelay"> <value>10000</value><!-- ms --> </property> <property name="repeatInterval"> <value>40000</value><!-- 每隔40秒調度1次 --> </property> </bean> <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource"> <ref bean="quartzDataSource" /><!--數據源引用指向,包含集羣所需的全部表 --> </property> <!--applicationContextSchedulerContextKey: 是org.springframework.scheduling.quartz.SchedulerFactoryBean這個類中把spring上下文以key/value的方式存放在了quartz的上下文中了 能夠用applicationContextSchedulerContextKey所定義的key獲得對應的spring上下文, 能夠看下源碼註釋--> <property name="applicationContextSchedulerContextKey" value="applicationContextKey" /> <property name="configLocation" value="classpath:quartz.properties" /> <!--用於指明quartz的配置文件的位置 --> <!-- job trigger 實例加載到 scheduler factory中 --> <property name="triggers"> <list> <ref bean="jobHelloTrigger" /> </list> </property> </bean> </beans>
#屬性可爲任何值,用在 JDBC JobStore 中來惟一標識實例,可是全部集羣節點中必須相同。 org.quartz.scheduler.instanceName = MyScheduler #爲 AUTO便可,基於主機名和時間戳來產生實例 ID。 org.quartz.scheduler.instanceId = AUTO org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 #線程繼承初始化線程的上下文類加載器 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true org.quartz.jobStore.misfireThreshold = 60000 #將任務持久化到數據中。由於集羣中節點依賴於數據庫來傳播 Scheduler 實例的狀態,你只能在使用 JDBC JobStore 時應用 Quartz 集羣。這意味着你必須使用 JobStoreTX 或是 JobStoreCMT 做爲 Job 存儲;你不能在集羣中使用 RAMJobStore。 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.tablePrefix = QRTZ_ #jobStore處理未按時觸發的Job的數量 org.quartz.jobStore.maxMisfiresToHandleAtATime=10 #告訴了 Scheduler 實例要它參與到一個集羣當中。這一屬性會貫穿於調度框架的始終,用於修改集羣環境中操做的默認行爲。 org.quartz.jobStore.isClustered = true #定義了Scheduler實例檢入到數據庫中的頻率(單位:毫秒)。Scheduler 檢查是否其餘的實例到了它們應當檢入的時候未檢入;這能指出一個失敗的 Scheduler 實例,且當前 Scheduler 會以此來接管任何執行失敗並可恢復的 Job。經過檢入操做,Scheduler 也會更新自身的狀態記錄。clusterChedkinInterval 越小,Scheduler 節點檢查失敗的 Scheduler 實例就越頻繁。默認值是 15000 (即15 秒)。 org.quartz.jobStore.clusterCheckinInterval=20000
package com.alfred.java.quartz.distschedule.springdemo01.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.alfred.java.quartz.distschedule.springdemo01.JobHello; public class JobHelloTest { public static void main(String[] args) { JobHello.setNUM("1"); ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); try { Thread.sleep(10000000); } catch (InterruptedException e) { e.printStackTrace(); } } }
# # Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # DROP TABLE IF EXISTS QRTZ_JOB_LISTENERS; DROP TABLE IF EXISTS QRTZ_TRIGGER_LISTENERS; DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS 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 BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT(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 BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) 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 BIGINT(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 BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(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;
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>2.5.6</spring.version> <quartz.version>1.8.6</quartz.version> <mysql.version>5.1.29</mysql.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>${quartz.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.0</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.3</version> </dependency> <dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>1.5.4</version> </dependency> </dependencies>
使用spring2.5.6+quartz1.8.6狀況下,按正常的spring配置方式調用,會提示一個序列化錯誤(java.io.NotSerializableException)數據庫
解決方式就是引入BeanInvokingJobDetailFactoryBean.java和MethodInvokingJobDetailFactoryBean.java(官方下載地址:https://jira.springsource.org/browse/SPR-3797),調用方式見上方applicationContext.xml。apache
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. * <p> * Use this Class instead of the MethodInvokingJobDetailBeanFactory Class provided by Spring when deploying to a web environment like Tomcat. * <p> * <b>Implementation</b><br> * 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. * <p> * 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. * <p> * 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). * <p> * <b>Example</b> * <code> * <ul> * <bean id="<i>exampleBean</i>" class="example.ExampleImpl"> </bean> <p> * <bean id="<i>exampleTrigger</i>" class="org.springframework.scheduling.quartz.CronTriggerBean"> * <ul> <i><!-- Execute exampleBean.fooBar() at 2am every day --></i><br> <property name="cronExpression" value="0 0 2 * * ?" /><br> <property name="jobDetail"> <ul> <bean class="frameworkx.springframework.scheduling.quartz.<b>BeanInvokingJobDetailFactoryBean</b>"> <ul> <property name="concurrent" value="<i>false</i>"/><br> <property name="targetBean" value="<i>exampleBean</i>" /><br> <property name="targetMethod" value="<i>fooBar</i>" /><br> <property name="arguments"> <ul> <list> <ul> <value>arg1Value</value><br> <value>arg2Value</value> </ul> <list> </ul> </property> </ul> </bean> </ul> </property> </ul> </bean> <p> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <ul> <property name="triggers"> <ul> <list> <ul> <ref bean="<i>exampleTrigger</i>" /> </ul> </list> </ul> </property> </ul> </bean> </ul> * </code> * 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 <code>fooBar</code>(String, String) method of the bean with id "<code>exampleBean</code>". Method <code>arguments</code> are optional. In this case there are two String arguments being provided to the <code>fooBar</code> method. The Scheduler is the heart of the whole operation; without it, nothing will happen. * <p> For more information on cronExpression visit <a href="http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html">http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html</a> * * <p> * <b>Troubleshooting</b> * <p> * <b>Error:</b> java.io.IOException: JobDataMap values must be Strings when the 'useProperties' property is set. Key of offending value: arguments<br> * <b>Solution:</b> do not set the <code>arguments</code> property when <code>org.quartz.jobstore.useProperty</code> is set to "true" in <code>quartz.properties</code>. * <p> * @author Stephen M. Wick * * @see #afterPropertiesSet() */ public class BeanInvokingJobDetailFactoryBean implements FactoryBean, BeanNameAware, InitializingBean, ApplicationContextAware { /** * Set by <code>setApplicationContext</code> when a BeanInvokingJobDetailFactoryBean is defined within a Spring ApplicationContext as a bean. * <p> * Used by the <code>execute</code> 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 <code>afterPropertiesSet</code> 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<i> - 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</i>) * <p> * Used by <code>afterPropertiesSet</code> 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 * <p>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 <a href="http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html">http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html</a> * @see #afterPropertiesSet() */ private boolean durable = false; /** * Used by <code>afterPropertiesSet</code> to set the JobDetail.volatile property. Default: false * <p>Volatility - if a job is volatile, it is not persisted between re-starts of the Quartz scheduler. * <p>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 <a href="http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html">http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html</a> * @see #afterPropertiesSet() */ private boolean volatility = false; /** * Used by <code>afterPropertiesSet</code> to set the JobDetail.requestsRecovery property. Default: false<BR> * <p>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 <a href="http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html">http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html</a> * @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 <code>afterPropertiesSet</code> to set the JobDetail.name property. * @see afterPropertiesSet() * @see JobDetail#setName(String) **/ private String beanName; /** * The JobDetail produced by the <code>afterPropertiesSet</code> method, and returned by the <code>getObject</code> 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. * <p> * Sets the <code>jobDetail</code> property to a new instance of JobDetail * <ul> * <li>jobDetail.name is set to <code>beanName</code><br> * <li>jobDetail.group is set to <code>group</code><br> * <li>jobDetail.jobClass is set to BeanInvokingJob.class or StatefulBeanInvokingJob.class depending on whether the <code>concurrent</code> property is set to true or false, respectively.<br> * <li>jobDetail.durability is set to <code>durable</code> * <li>jobDetail.volatility is set to <code>volatility</code> * <li>jobDetail.requestsRecovery is set to <code>shouldRecover</code> * <li>jobDetail.jobDataMap["targetBean"] is set to <code>targetBean</code> * <li>jobDetail.jobDataMap["targetMethod"] is set to <code>targetMethod</code> * <li>jobDetail.jobDataMap["arguments"] is set to <code>arguments</code> * <li>Each JobListener name in <code>jobListenerNames</code> is added to the <code>jobDetail</code> object. * </ul> * <p> * Logging occurs at the DEBUG and INFO levels; 4 lines at the DEBUG level, and 1 line at the INFO level. * <ul> * <li>DEBUG: start * <li>DEBUG: Creating JobDetail <code>{beanName}</code> * <li>DEBUG: Registering JobListener names with JobDetail object <code>{beanName}</code> * <li>INFO: Created JobDetail: <code>{jobDetail}</code>; targetBean: <code>{targetBean}</code>; targetMethod: <code>{targetMethod}</code>; arguments: <code>{arguments}</code>; * <li>DEBUG: end * </ul> * @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 < this.jobListenerNames.length; i++) { this.jobDetail.addJobListener(this.jobListenerNames[i]); } } logger.info("Created JobDetail: "+jobDetail+"; targetBean: "+targetBean+"; targetMethod: "+targetMethod+"; arguments: "+arguments+";"); } finally { logger.debug("end"); } } /** * Setter for the concurrent property. * * @param concurrent * @see #concurrent */ public void setConcurrent(boolean concurrent) { this.concurrent = concurrent; } /** * setter for the durable property. * * @param durable * * @see #durable */ public void setDurable(boolean durable) { this.durable = durable; } /** * setter for the group property. * * @param group * * @see #group */ public void setGroup(String group) { this.group = group; } /** * setter for the {@link #jobListenerNames} property. * * @param jobListenerNames * @see #jobListenerNames */ public void setJobListenerNames(String[] jobListenerNames) { this.jobListenerNames = jobListenerNames; } /** * setter for the {@link #shouldRecover} property. * * @param shouldRecover * @see #shouldRecover */ public void setShouldRecover(boolean shouldRecover) { this.shouldRecover = shouldRecover; } /** * setter for the {@link #volatility} property. * * @param volatility * @see #volatility */ public void setVolatility(boolean volatility) { this.volatility = volatility; } /** * Set the Spring ApplicationContext in which this class has been deployed. * <p> * 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. * <p> * The only entries this Job expects in the JobDataMap are "targetBean" and "targetMethod".<br> * - It uses the value of the <code>targetBean</code> entry to get the desired bean from the Spring ApplicationContext.<br> * - It uses the value of the <code>targetMethod</code> entry to determine which method of the Bean (identified by targetBean) to invoke. * <p> * It uses the static ApplicationContext in the BeanInvokingJobDetailFactoryBean, * which is ApplicationContextAware, to get the Bean with which to invoke the method. * <p> * 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, <code>execute</code> invokes a method on a bean deployed within the scheduler's Spring ApplicationContext. * <p> * <b>Implementation</b><br> * The bean is identified by the "targetBean" entry in the JobDataMap of the JobExecutionContext provided.<br> * The method is identified by the "targetMethod" entry in the JobDataMap of the JobExecutionContext provided.<br> * <p> * 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. * <p> * 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. } }
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. * <p> * Use this Class instead of the MethodInvokingJobDetailBeanFactory Class provided by Spring when deploying to a web environment like Tomcat. * <p> * <b>Implementation</b><br> * 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. * <p> * A method can be invoked one of several ways: * <ul> * <li>The name of the Class to invoke (targetClass) and the static method to invoke (targetMethod) can be specified. * <li>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"). * <li>The Class and static Method to invoke can be specified in one property (staticMethod). example: staticMethod = "example.ExampleClass.someStaticMethod" * <br><b>Note:</b> An Object[] of method arguments can be specified (arguments), but the Objects must be Serializable if concurrent=="false". * </ul> * <p> * 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). * <p> * <b>Example</b> * <code> * <ul> * <bean id="<i>exampleTrigger</i>" class="org.springframework.scheduling.quartz.CronTriggerBean"> * <ul> <i><!-- Execute example.ExampleImpl.fooBar() at 2am every day --></i><br> <property name="<a href="http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html">cronExpression</a>" value="0 0 2 * * ?" /><br> <property name="jobDetail"> <ul> <bean class="frameworkx.springframework.scheduling.quartz.<b>MethodInvokingJobDetailFactoryBean</b>"> <ul> <property name="concurrent" value="<i>false</i>"/><br> <property name="targetClass" value="<i>example.ExampleImpl</i>" /><br> <property name="targetMethod" value="<i>fooBar</i>" /> </ul> </bean> </ul> </property> </ul> </bean> <p> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <ul> <property name="triggers"> <ul> <list> <ul> <ref bean="<i>exampleTrigger</i>" /> </ul> </list> </ul> </property> </ul> </bean> </ul> * </code> * 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 <code>fooBar</code>() method of the "<code>example.ExampleImpl</code>" Class. The Scheduler is the heart of the whole operation; without it, nothing will happen. * <p> * For more information on <code>cronExpression</code> syntax visit <a href="http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html">http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html</a> * * @author Stephen M. Wick * * @see #afterPropertiesSet() */ public class MethodInvokingJobDetailFactoryBean implements FactoryBean, BeanNameAware, InitializingBean { private Log logger = LogFactory.getLog(getClass()); /** * The JobDetail produced by the <code>afterPropertiesSet</code> 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<i> - 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</i>) * <p> * Used by <code>afterPropertiesSet</code> 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 * <p>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 <a href="http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html">http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html</a> * @see #afterPropertiesSet() */ private boolean durable = false; /** * Used by <code>afterPropertiesSet</code> to set the JobDetail.volatile property. Default: false * <p>Volatility - if a job is volatile, it is not persisted between re-starts of the Quartz scheduler. * <p>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 <a href="http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html">http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html</a> * @see #afterPropertiesSet() */ private boolean volatility = false; /** * Used by <code>afterPropertiesSet</code> to set the JobDetail.requestsRecovery property. Default: false<BR> * <p>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 <a href="http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html">http://www.opensymphony.com/quartz/wikidocs/TutorialLesson3.html</a> * @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 <code>afterPropertiesSet</code> to set the JobDetail.name property. * @see afterPropertiesSet() * @see JobDetail#setName(String) **/ private String beanName; /** * The JobDetail produced by the <code>afterPropertiesSet</code> method, and returned by the <code>getObject</code> 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. * <p> * {@link #targetClass} or targetObject must be set, but not both. * <p> * 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. * <p> * 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. * <p> * {@link #targetMethod} or staticMethod should be set, but not both. */ private String staticMethod; /** * Method arguments provided to the {@link #targetMethod} or {@link #staticMethod} specified. * <p> * All arguments must be Serializable when {@link #concurrent} is set to false. * <p> * 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. * <p> * Sets the <code>jobDetail</code> property to a new instance of JobDetail * <ul> * <li>jobDetail.name is set to <code>beanName</code><br> * <li>jobDetail.group is set to <code>group</code><br> * <li>jobDetail.jobClass is set to MethodInvokingJob.class or StatefulMethodInvokingJob.class depending on whether the <code>concurrent</code> property is set to true or false, respectively.<br> * <li>jobDetail.durability is set to <code>durable</code> * <li>jobDetail.volatility is set to <code>volatility</code> * <li>jobDetail.requestsRecovery is set to <code>shouldRecover</code> * <li>jobDetail.jobDataMap["targetClass"] is set to <code>targetClass</code> * <li>jobDetail.jobDataMap["targetMethod"] is set to <code>targetMethod</code> * <li>Each JobListener name in <code>jobListenerNames</code> is added to the <code>jobDetail</code> object. * </ul> * <p> * Logging occurs at the DEBUG and INFO levels; 4 lines at the DEBUG level, and 1 line at the INFO level. * <ul> * <li>DEBUG: start * <li>DEBUG: Creating JobDetail <code>{beanName}</code> * <li>DEBUG: Registering JobListener names with JobDetail object <code>{beanName}</code> * <li>INFO: Created JobDetail: <code>{jobDetail}</code>; targetClass: <code>{targetClass}</code>; targetMethod: <code>{targetMethod}</code>; * <li>DEBUG: end * </ul> * @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 < 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. * * @param concurrent * @see #concurrent */ public void setConcurrent(boolean concurrent) { this.concurrent = concurrent; } /** * setter for the durable property. * * @param durable * * @see #durable */ public void setDurable(boolean durable) { this.durable = durable; } /** * setter for the group property. * * @param group * * @see #group */ public void setGroup(String group) { this.group = group; } /** * setter for the {@link #jobListenerNames} property. * * @param jobListenerNames * @see #jobListenerNames */ public void setJobListenerNames(String[] jobListenerNames) { this.jobListenerNames = jobListenerNames; } /** * setter for the {@link #shouldRecover} property. * * @param shouldRecover * @see #shouldRecover */ public void setShouldRecover(boolean shouldRecover) { this.shouldRecover = shouldRecover; } /** * setter for the {@link #volatility} property. * * @param volatility * @see #volatility */ public void setVolatility(boolean volatility) { this.volatility = volatility; } /** * This is a cluster safe Job designed to invoke a method on any bean defined within the same Spring * ApplicationContext. * <p> * The only entries this Job expects in the JobDataMap are "targetClass" and "targetMethod".<br> * - It uses the value of the <code>targetClass</code> entry to get the desired bean from the Spring ApplicationContext.<br> * - It uses the value of the <code>targetMethod</code> entry to determine which method of the Bean (identified by targetClass) to invoke. * <p> * It uses the static ApplicationContext in the MethodInvokingJobDetailFactoryBean, * which is ApplicationContextAware, to get the Bean with which to invoke the method. * <p> * 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, <code>execute</code> invokes a method on a Class or Object in the JobExecutionContext provided. * <p> * <b>Implementation</b><br> * 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.<br> * 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 <code>concurrent</code> is set to false.<br> * The method is identified by the "targetMethod" entry in the JobDataMap of the JobExecutionContext provided.<br> * 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")<br> * 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 <code>concurrent</code> is set to false. * <p> * 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("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 = 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) { 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; } }