Spring+Quartz定时任务集群环境下部署的解决方法

* 在集群环境下多节点运行定时Quartz定任务,就会存在重复处理任务的现象,为解决这一问题,下面我将介绍使用 Quartz 的 TASK ( 12 张表)实例化到数据库,基于数据库自动管理协调每个节点的定时任务的启动、关闭。*

原理:
集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。目前集群只能工作在JDBC-JobStore(JobStore TX或者JobStoreCMT)方式下,从本质上来说,是使集群上的每一个节点通过共享同一个数据库来工作的(Quartz通过启动两个维护线程来维护数据库状态实现集群管理,一个是检测节点状态线程,一个是恢复任务线程)。
负载平衡是自动完成的,集群的每个节点会尽快触发任务。当一个触发器的触发时间到达时,第一个节点将会获得任务(通过锁定),成为执行任务的节点。
故障切换的发生是在当一个节点正在执行一个或者多个任务失败的时候。当一个节点失败了,其他的节点会检测到并且标 识在失败节点上正在进行的数据库中的任务。任何被标记为可恢复(任务详细信息的”requests recovery”属性)的任务都会被其他的节点重新执行。没有标记可恢复的任务只会被释放出来,将会在下次相关触发器触发时执行。

一、在Maven项目中的pool.xml导入以下jar包,注意版本

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>1.8.5</version>
    </dependency>
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>commons-pool</groupId>
        <artifactId>commons-pool</artifactId>
        <version>1.6</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>

二、在mysql数据库中导入以下SQL脚本,共12张表

#
# 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;

表与字段说明:

quartz框架中T_TASK_TRIGGERS表 TRIGGER_STATE 字段显示任务的属性大概状态有这几种:
WAITING:等待
PAUSED:暂停
ACQUIRED:正常执行
BLOCKED:阻塞
ERROR:错误
主要使用以上这几种状态控制调度各节点定时任务

QRTZ_CALENDARS 以 Blob 类型存储 Quartz 的 Calendar 信息   
QRTZ_CRON_TRIGGERS 存储 Cron Trigger,包括Cron表达式和时区信息   
QRTZ_FIRED_TRIGGERS 存储与已触发的 Trigger 相关的状态信息,以及相联 Job的执行信息QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的 Trigger组的信息   
QRTZ_SCHEDULER_STATE 存储少量的有关 Scheduler 的状态信息,和别的Scheduler实例(假如是用于一个集群中)   
QRTZ_LOCKS 存储程序的悲观锁的信息(假如使用了悲观锁)   
QRTZ_JOB_DETAILS 存储每一个已配置的 Job 的详细信息   
QRTZ_JOB_LISTENERS 存储有关已配置的 JobListener的信息   
QRTZ_SIMPLE_TRIGGERS存储简单的Trigger,包括重复次数,间隔,以及已触的次数   
QRTZ_BLOG_TRIGGERS Trigger 作为 Blob 类型存储(用于 Quartz 用户用JDBC创建他们自己定制的 Trigger 类型,JobStore并不知道如何存储实例的时候)   
QRTZ_TRIGGER_LISTENERS 存储已配置的 TriggerListener的信息   
QRTZ_TRIGGERS 存储已配置的 Trigger 的信息  

表qrtz_job_details:保存job详细信息,该表需要用户根据实际情况初始化   
job_name:集群中job的名字,该名字用户自己可以随意定制,无强行要求   
job_group:集群中job的所属组的名字,该名字用户自己随意定制,无强行要求   
job_class_name:集群中个notejob实现类的完全包名,quartz就是根据这个路径到classpath找到该job类   
is_durable:是否持久化,把该属性设置为1,quartz会把job持久化到数据库中   
job_data:一个blob字段,存放持久化job对象   

表qrtz_triggers: 保存trigger信息   
trigger_name:trigger的名字,该名字用户自己可以随意定制,无强行要求   
trigger_group:trigger所属组的名字,该名字用户自己随意定制,无强行要求   
job_name:qrtz_job_details表job_name的外键   
job_group:qrtz_job_details表job_group的外键   
trigger_state:当前trigger状态,设置为ACQUIRED,如果设置为WAITING,则job不会触发   
trigger_cron:触发器类型,使用cron表达式   

表qrtz_cron_triggers:存储cron表达式表   
trigger_name:qrtz_triggers表trigger_name的外键   
trigger_group:qrtz_triggers表trigger_group的外键   
cron_expression:cron表达式   

表qrtz_scheduler_state:存储集群中note实例信息,quartz会定时读取该表的信息判断集群中每个实例的当前状态   
instance_name:之前配置文件中org.quartz.scheduler.instanceId配置的名字,就会写入该字段,如果设置为AUTO,quartz会根据物理机名和当前时间产生一个名字   
last_checkin_time:上次检查时间   
checkin_interval:检查间隔时间 

三、在项目中创建BootstrapJob类(序列化引导类)

/**
 * 引导Job,通过Spring容器获取任务的Job,根据注入的targetJob,该Job必须实现Job2接口
 */
public class BootstrapJob implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;


    private String targetJob ; 

    public void executeInternal(ApplicationContext cxt) {
        QuartzJob job = (QuartzJob)cxt.getBean(this.targetJob);
        job.executeInternal() ;
    }

    public String getTargetJob() {
        return targetJob;
    }

    public void setTargetJob(String targetJob) {
        this.targetJob = targetJob;
    }
}

四、在项目中创建QuartzJob接口(处理任务的核心)

/**
 Quartz 与 Spring 集成时,自定义的Job可以拥有Spring的上下文,
* 因此定义了该接口,自定义的Job需要实现该接口,并Override  executeInternal方法,
* 这样解决了Quartz 与Spring 在集群环境下,可以不需要对每个JOB序列化,
* 只需要在executeInternal获取Spring 上下文中的target job bean.
* 调用其相关的处理函数,来处理任务
 */
public interface QuartzJob extends Serializable{

    /**
     * 处理任务的核心函数
     * 
     * @param cxt Spring 上下文
     */
    void executeInternal();

}

五、原MethodInvokingJobDetailFactoryBean类是不支持序列化的,所以需要重写MethodInvokingJobDetailFactoryBean类


/**
 * Quartz 方法重写 
 * 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");
                if(targetObject instanceof BootstrapJob){
                    //Job2 job = (Job2)targetObject;
                    //job.executeInternal(context.getScheduler().getContext().)
                    ApplicationContext ac = (ApplicationContext)context.getScheduler().getContext().get("applicationContext");
                    BootstrapJob target = (BootstrapJob)targetObject ;
                    target.executeInternal(ac);
                }else{
                    //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;
    }
}

六、创建quartz.properties,数据源连接配置

 #==============================================================    
#配置主要调度程序属性   
#==============================================================     
org.quartz.scheduler.instanceName = quartzScheduler  
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.class = org.quartz.impl.jdbcjobstore.JobStoreTX  
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate  
org.quartz.jobStore.tablePrefix = QRTZ_  
org.quartz.jobStore.isClustered = true  
org.quartz.jobStore.clusterCheckinInterval = 20000    
org.quartz.jobStore.dataSource = myDS  
#值为 True 时告诉 Quartz (当使用 JobStoreTX 或 CMT 时) 调用 JDBC 连接的 setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 方法。这能助于防止某些数据库在高负荷和长事物时的锁超时。
org.quartz.jobStore.txIsolationLevelSerializable = true
org.quartz.jobStore.selectWithLockSQL = "SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE"

#==============================================================    
#trigger历史日志记录   
#==============================================================   
org.quartz.plugin.triggHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.triggHistory.triggerFiredMessage=Trigger {1}.{0} fired job {6}.{5} at:{4, date, HH:mm:ss MM/dd/yyyy}
org.quartz.plugin.triggHistory.triggerCompleteMessage =Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy}

#==============================================================    
#数据库连接信息
#==============================================================   
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver  
org.quartz.dataSource.myDS.URL = jdbc\:mysql\://127.0.0.1\:3306/test?useUnicode\=true&characterEncoding\=UTF-8  
org.quartz.dataSource.myDS.user =test
org.quartz.dataSource.myDS.password =test
org.quartz.dataSource.myDS.maxConnections =30  

配置说明:

#调度标识名 集群中每一个实例都必须使用相同的名称 org.quartz.scheduler.instanceName:scheduler  
#ID设置为自动获取 每一个必须不同 org.quartz.scheduler.instanceId :AUTO  
#数据保存方式为持久化 org.quartz.jobStore.class :org.quartz.impl.jdbcjobstore.JobStoreTX  
#数据库平台 org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate#数据库别名 随便取org.quartz.jobStore.dataSource : myXADS  
#表的前缀 org.quartz.jobStore.tablePrefix : QRTZ_  
#设置为TRUE不会出现序列化非字符串类到 BLOB 时产生的类版本问题org.quartz.jobStore.useProperties : true  
#加入集群 org.quartz.jobStore.isClustered : true  
#调度实例失效的检查时间间隔 org.quartz.jobStore.clusterCheckinInterval:20000   
#容许的最大作业延长时间 org.quartz.jobStore.misfireThreshold :60000  
#ThreadPool 实现的类名 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.dataSource.myXADS.jndiURL: CT  
#jbdi类名 org.quartz.dataSource.myXADS.java.naming.factory.initial :weblogic.jndi.WLInitialContextFactory#URLorg.quartz.dataSource.myXADS.java.naming.provider.url:=t3://localhost:7001  
【注】:在J2EE工程中如果想用数据库管理Quartz的相关信息,就一定要配置数据源,这是Quartz的要求。

七、Quartz定时任务xml配置文件

例:

    <!-- 引导Job 序列化 引入BootstrapJob.java 注意多个任务bean名不要重复-->
    <bean id="testBootstrapJob" class="myQuarter.BootstrapJob">
        <property name="targetJob" value="myJob" /> myJob为任务执行bean
    </bean>
    <!-- 这里引入重写的MethodInvokingJobDetailFactoryBean类-->
    <bean id="testTask" class="myQuarter.MethodInvokingJobDetailFactoryBean">
        <!--false表示等上一个任务执行完后再开启新的任务 -->
        <property name="concurrent" value="false" />
        <property name="targetObject" ref="testBootstrapJob" />
    </bean>

    <bean id="testTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail" ref="testTask" />
         <property name="cronExpression">
         <!-- 每天执行任务调度 -->
         <value>0 0/1 * * * ?</value>
        </property>
    </bean>
    <!--  Quartz的调度工厂,调度工厂只能有一个,多个调度任务在list中添加 -->
    <bean id="startJob" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="configLocation" value="classpath:quartz.properties" /> 
           <!--需要overwrite已经存在的job,如果需要动态的修改已经存在的job,就需要设置为true,否则会以数据库中已经存在的为准 -->    
        <property name="overwriteExistingJobs" value="true" />

         <!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 -->    
        <property name="startupDelay" value="3" />    
        <!-- 设置自动启动 -->    
        <property name="autoStartup" value="true" />  
        <property name="triggers">
            <list>
                <!-- 所有的调度列表 -->
                <ref bean="testTrigger"/>

            </list>
        </property>
        <!-- 就是下面这句,因为该 bean 只能使用类反射来重构 -->
            <property name="applicationContextSchedulerContextKey" value="applicationContext" /> 
    </bean>

注:如果Quartz定时任务xml配置发生改变,目前是需要清一下上一次执行定时任务存到T_TASK相关表中的数据,否则可能任务无法执行。因为当启动项目时Quartz会将一些配置存储到数据库。

delete from qrtz_blob_triggers;
delete from qrtz_calendars;
delete from qrtz_fired_triggers;
delete from qrtz_job_listeners;
delete from qrtz_paused_trigger_grps;
delete from qrtz_scheduler_state;
delete from qrtz_simple_triggers;
delete from qrtz_cron_triggers;
delete from qrtz_triggers;
delete from qrtz_job_details;

关于一些配置说明可参考http://blog.csdn.net/evankaka/article/details/45540885

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值