zoukankan      html  css  js  c++  java
  • 信步漫谈之Quartz—分布式调度(整合spring早期版本【低于spring3.1】)

    一、环境

    使用的jar包:spring2.5.6、quartz1.8.6

    二、注意点

    因为spring内置的quartz版本变化,所以存在spring和quartz版本接口兼容情况,如下:

    1)spring3.1之后的版本(包含)兼容quartz2.X(这里有个坑,网络资料是spring3.0对应quartz1.X,但也有人说支持quartz2.X,只要不在spring的配置文件中使用,直接在程序中调用就可以(废话),具体需要验证)

    2)spring3.1之前的版本兼容quartz1.X

    三、实现方案

    Demo程序目录结构:

    clipboard

    Demo核心程序源码:

    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;
    	}
    
    }
    JobHello.java
    <?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>
    applicationContext.xml
    #属性可为任何值,用在 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
    quartz.properties
    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();
    		}
    	}
    
    }
    
    JobHelloTest.java
    #
    # 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;
    
    tables_mysql.sql
    <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>
    pom.xml

    使用spring2.5.6+quartz1.8.6情况下,按正常的spring配置方式调用,会提示一个序列化错误(java.io.NotSerializableException)

    解决方式就是引入BeanInvokingJobDetailFactoryBean.java和MethodInvokingJobDetailFactoryBean.java(官方下载地址:https://jira.springsource.org/browse/SPR-3797),调用方式见上方applicationContext.xml。

    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>
     * 	&lt;bean id="<i>exampleBean</i>" class="example.ExampleImpl"&gt;
    	&lt;/bean&gt;
    	<p>
     * 	&lt;bean id="<i>exampleTrigger</i>" class="org.springframework.scheduling.quartz.CronTriggerBean"&gt;
     * <ul>
    	<i>&lt;!-- Execute exampleBean.fooBar() at 2am every day --&gt;</i><br>
    	&lt;property name="cronExpression" value="0 0 2 * * ?" /&gt;<br>
    	&lt;property name="jobDetail"&gt;
    	<ul>
    	&lt;bean class="frameworkx.springframework.scheduling.quartz.<b>BeanInvokingJobDetailFactoryBean</b>"&gt;
    	<ul>
    	&lt;property name="concurrent" value="<i>false</i>"/&gt;<br>
    	&lt;property name="targetBean" value="<i>exampleBean</i>" /&gt;<br>
    	&lt;property name="targetMethod" value="<i>fooBar</i>" /&gt;<br>
    	&lt;property name="arguments"&gt;
    	<ul>
    		&lt;list&gt;
    		<ul>
    			&lt;value&gt;arg1Value&lt;/value&gt;<br>
    			&lt;value&gt;arg2Value&lt;/value&gt;
    		</ul>
    		&lt;list&gt;
    	</ul>
    	&lt;/property&gt;
    	</ul>
    	&lt;/bean&gt;
    	</ul>
    	&lt;/property&gt;
    	</ul>
    	&lt;/bean&gt;
    	<p>
    	&lt;bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"&gt;
    	<ul>
    	&lt;property name="triggers"&gt;
    	<ul>
    	&lt;list&gt;
    	<ul>
    	&lt;ref bean="<i>exampleTrigger</i>" /&gt;
    	</ul>
    	&lt;/list&gt;
    	</ul>
    	&lt;/property&gt;
    	</ul>
    	&lt;/bean&gt;
    	</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.
    	}
    }
    BeanInvokingJobDetailFactoryBean.java
    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>
     * 	&lt;bean id="<i>exampleTrigger</i>" class="org.springframework.scheduling.quartz.CronTriggerBean"&gt;
     * <ul>
    	<i>&lt;!-- Execute example.ExampleImpl.fooBar() at 2am every day --&gt;</i><br>
    	&lt;property name="<a href="http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html">cronExpression</a>" value="0 0 2 * * ?" /&gt;<br>
    	&lt;property name="jobDetail"&gt;
    	<ul>
    	&lt;bean class="frameworkx.springframework.scheduling.quartz.<b>MethodInvokingJobDetailFactoryBean</b>"&gt;
    	<ul>
    	&lt;property name="concurrent" value="<i>false</i>"/&gt;<br>
    	&lt;property name="targetClass" value="<i>example.ExampleImpl</i>" /&gt;<br>
    	&lt;property name="targetMethod" value="<i>fooBar</i>" /&gt;
    	</ul>
    	&lt;/bean&gt;
    	</ul>
    	&lt;/property&gt;
    	</ul>
    	&lt;/bean&gt;
    	<p>
    	&lt;bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"&gt;
    	<ul>
    	&lt;property name="triggers"&gt;
    	<ul>
    	&lt;list&gt;
    	<ul>
    	&lt;ref bean="<i>exampleTrigger</i>" /&gt;
    	</ul>
    	&lt;/list&gt;
    	</ul>
    	&lt;/property&gt;
    	</ul>
    	&lt;/bean&gt;
    	</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;
    	}
    }
    MethodInvokingJobDetailFactoryBean.java
  • 相关阅读:
    SpringBoot异步处理请求
    5本最佳的 Java 面向对象理论和设计模式的书籍
    彻底弄懂 HTTP 缓存机制 —— 基于缓存策略三要素分解法
    Java 性能优化的五大技巧
    Java 8 最佳技巧
    Java 并发的四种风味:Thread、Executor、ForkJoin 和 Actor
    在 Java 8 中避免 Null 检查
    关于创建java线程池问题的思考
    LuoguP1858 多人背包(DP)
    Luogu[YNOI2019]排序(DP,线段树)
  • 原文地址:https://www.cnblogs.com/alfredinchange/p/10353914.html
Copyright © 2011-2022 走看看