Spring整合Quartz框架實現分散式定時任務
1、叢集使用定時任務的問題:
目前大部分在叢集中處理定時任務的方式不是正真的分散式處理方式,而是一種偽分散式,這種方式存在一個明顯的缺陷就是當叢集中機器宕機, 那麼整個定時任務就會掛掉或者不能一次性跑完,會對業務產生嚴重的影響。
而且在叢集環境中,同樣的定時任務,在叢集中的每臺伺服器都會執行,這樣定時任務就會重複執行,不但會增加伺服器的負擔,還會因為定時任務重複執行造成額外的不可預期的錯誤。
解決方案是: 根據叢集的數量,把定時任務中的任務平均分到叢集中的每臺機器上(這裡的平均分是指以前多個定時任務本來是在一臺機器上執行,先在人為的把這些任務分成幾部分,讓所有的機器分別去執行這些任務) 這就是採用了分散式定時任務來進行處理。 另外一種解決方式: 使用Quartz框架,在叢集環境下,通過資料庫鎖機制來實現定時任務的執行,下面會介紹。
2、Quartz 中叢集如何工作
一個 Quartz 叢集中的每個節點是一個獨立的 Quartz 應用,它又管理著其他的節點。意思是你必須對每個節點分別啟動或停止。不像許多應用伺服器的叢集,獨立的 Quartz 節點並不與另一其的節點或是管理節點通訊。Quartz 應用是通過資料庫表來感知到另一應用的。
圖:表示了每個節點直接與資料庫通訊,若離開資料庫將對其他節點一無所知
3、程式碼實現
3.1 建立Quartz資料庫表
因為Quartz 叢集依賴於資料庫,所以必須首先建立Quartz資料庫表。Quartz 包括了所有被支援的資料庫平臺的 SQL 指令碼。在 <quartz_home>/docs/dbTables 目錄下找到那些 SQL 指令碼,這裡的 <quartz_home> 是解壓 Quartz 分發包後的目錄。
這裡採用的Quartz 2.2.3版本,總共11張表,不同版本,表個數可能不同。資料庫為mysql,用tables_mysql_innodb.sql建立資料庫表。
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_SIMPROP_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( SCHED_NAME VARCHAR(120) NOT NULL, 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_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) 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 (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, 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 (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)) ENGINE=InnoDB; CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)) ENGINE=InnoDB; CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, SCHED_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_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID)) ENGINE=InnoDB; CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)) ENGINE=InnoDB; CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME)) ENGINE=InnoDB; CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); commit;
pom依賴:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
3、2 資料庫連線池
#jdbc.driver=com.mysql.jdbc.Driver
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/clouddb01?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT
jdbc.username=root
jdbc.password=123456
3、3 applicationContext:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 屬性檔案讀入 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:db.properties</value>
</list>
</property>
</bean>
<!-- 資料來源定義,使用c3p0 連線池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialPoolSize" value="5" />
<property name="minPoolSize" value="10" />
<property name="maxPoolSize" value="20" />
<property name="acquireIncrement" value="2" />
<property name="maxIdleTime" value="1800" />
</bean>
<!-- 配置註解驅動 -->
<mvc:annotation-driven />
<!-- 配置包掃描器,掃描所有帶@Service註解的類 -->
<context:component-scan base-package="com.th" />
</beans>
3、4 實際業務處理類UserService:
package com.th.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void test1() {
System.out.println("執行任務1...");
}
public void test2() {
System.out.println("執行任務2...");
}
}
3、5建立job類:
package com.th.quartz;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import com.th.service.UserService;
@PersistJobDataAfterExecution
@DisallowConcurrentExecution // 不允許併發執行
public class MyQuartzJob extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(MyQuartzJob.class);
@Override
protected void executeInternal(JobExecutionContext jobexecutioncontext) throws JobExecutionException {
UserService simpleService = getApplicationContext(jobexecutioncontext).getBean("userService", UserService.class);
simpleService.test1();
}
private ApplicationContext getApplicationContext(final JobExecutionContext jobexecutioncontext) {
try {
return (ApplicationContext) jobexecutioncontext.getScheduler().getContext().get("applicationContextKey");
} catch (SchedulerException e) {
logger.error("jobexecutioncontext.getScheduler().getContext() error!", e);
throw new RuntimeException(e);
}
}
}
3、6 配置 Quartz 使用叢集
quartz.properties:
org.quartz.scheduler.instanceName = TestScheduler1
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
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.maxMisfiresToHandleAtATime=10
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.scheduler.instanceName屬性可為任何值,用在 JDBC JobStore 中來唯一標識例項,但是所有叢集節點中必須相同。
org.quartz.scheduler.instanceId 屬性為 AUTO即可,基於主機名和時間戳來產生例項 ID。
org.quartz.jobStore.class屬性為 JobStoreTX,將任務持久化到資料中。因為叢集中節點依賴於資料庫來傳播 Scheduler 例項的狀態,你只能在使用 JDBC JobStore 時應用 Quartz 叢集。這意味著你必須使用 JobStoreTX 或是 JobStoreCMT 作為 Job 儲存;你不能在叢集中使用 RAMJobStore。
org.quartz.jobStore.isClustered 屬性為 true,你就告訴了 Scheduler 例項要它參與到一個叢集當中。這一屬性會貫穿於排程框架的始終,用於修改叢集環境中操作的預設行為。
org.quartz.jobStore.clusterCheckinInterval 屬性定義了Scheduler 例項檢入到資料庫中的頻率(單位:毫秒)。Scheduler 檢查是否其他的例項到了它們應當檢入的時候未檢入;這能指出一個失敗的 Scheduler 例項,且當前 Scheduler 會以此來接管任何執行失敗並可恢復的 Job。通過檢入操作,Scheduler 也會更新自身的狀態記錄。clusterChedkinInterval 越小,Scheduler 節點檢查失敗的 Scheduler 例項就越頻繁。預設值是 15000 (即15 秒)
3、7 配置applicationContext-quartz.xml檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
<property name="configLocation" value="classpath:quartz.properties" />
<property name="triggers">
<list>
<ref bean="trigger1" />
</list>
</property>
</bean>
<bean id="jobDetail1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass">
<value>com.th.quartz.MyQuartzJob</value>
</property>
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
</bean>
<bean id="trigger1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail1" />
<property name="cronExpression" value="0/2 * * * * ?" />
</bean>
</beans>
dataSource:專案中用到的資料來源,裡面包含了quartz用到的11張資料庫表;
applicationContextSchedulerContextKey: 是org.springframework.scheduling.quartz.SchedulerFactoryBean這個類中把spring上下 文以key/value的方式存放在了SchedulerContext中了,可以用applicationContextSchedulerContextKey所 定義的key得到對應spring 的ApplicationContext;
configLocation:用於指明quartz的配置檔案的位置
requestsRecovery
requestsRecovery屬性必須設定為 true,當Quartz服務被中止後,再次啟動或叢集中其他機器接手任務時會嘗試恢復執行之前未完成的所有任務。
4、測試
在兩臺tomcat下面部署上面的專案,同時啟動
測試結果:當兩臺伺服器都執行的時候,先啟動的tomcat伺服器獲得執行權,會一直執行test1方法,另外臺tomcat不會做任何事。當我們關閉第一臺tomcat後,第二臺tomcat開始執行定時任務,到這裡 測試成功
5、spring使用quartz第二種配置方式
第一種是任務類繼承QuartzJobBean,第二種則是在配置檔案裡定義任務類和要執行的方法,類和方法仍然是普通類。 很顯然,第二種方式遠比第一種方式來的靈活。我們發現上面採用的就是第一種方法,下面說下第二種方法。
配置XML如下:
方式二:使用MethodInvokingJobDetailFactoryBean,任務類可以不實現Job介面,通過targetMethod指定呼叫方法 -----------------
-定義目標bean和bean中的方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
<property name="configLocation" value="classpath:quartz.properties" />
<property name="triggers">
<list>
<ref bean="trigger1" />
</list>
</property>
</bean>
<!-- <bean id="jobDetail1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass">
<value>com.th.quartz.MyQuartzJob</value>
</property>
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
</bean>
<bean id="trigger1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail1" />
<property name="cronExpression" value="0/2 * * * * ?" />
</bean> -->
<!-- 實際業務處理類 -->
<bean id="userServiceJob" class="com.th.service.UserService" />
<bean id="userServiceJobMethod" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="userServiceJob"></property>
<!-- 業務處理的實際方法 -->
<property name="targetMethod" value="test1"></property>
</bean>
<!-- 排程觸發器 -->
<bean name="trigger1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="userServiceJobMethod"></property>
<property name="cronExpression" value="0/2 * * * * ?"></property>
</bean>
</beans>