JobDataMap被用來保存一系列的(序列化的)對象,這些對象在Job執行時能夠獲得。JobDataMap是Java Map接口的一個實現,並且還增長了一些存儲和讀取主類型數據的便捷方法。
若是使用一個持久的JobStore,那麼必須注意存放在JobDataMap中的內容。由於放入JobDataMap中的內容將被序列化,並且 容易出現類型轉換問題。很明顯,標準Java類型將是很是安全的,但除此以外的類型,任什麼時候候,只要有人改變了你要序列化其實例的類的定義,就要注意是否 打破了程序的兼容性。另外,你能夠對JobStore和JobDataMap採用一種使用模式:就是隻把主類型和String類型存放在Map中,這樣就 能夠減小後面序列化的問題。 java
Triggers也能夠有JobDataMaps與之相關聯。當scheduler中的Job被多個有規律或者重複觸發的Triggers所使用時很是有用。對於每次獨立的觸發,你可爲Job提供不一樣的輸入數據。
從Job執行時的JobExecutionContext中取得JobDataMap是慣用手段,它融合了從JobDetail和從Trigger中獲的JobDataMap,當有相同名字的鍵時,它用後者的值覆蓋前者值。web
靜態處理方式:spring
在集羣環境下,你們會碰到一直困擾的問題,即多個 APP 下如何用 quartz 協調處理自動化 JOB 。sql
你們想象一下,如今有 A , B , C3 臺機器同時做爲集羣服務器對外統一提供 SERVICE : 數據庫
A , B , C 3 臺機器上各有一個 QUARTZ ,他們會按照即定的 SCHEDULE 自動執行各自的任務。安全
咱們先不說實現什麼功能,就說這樣的架構其實有點像多線程。服務器
那多線程裏就會存在「資源競爭」的問題,便可能產生髒讀,髒寫,因爲三臺 APP SERVER 裏都有 QUARTZ ,所以會存在重複處理 TASK 的現象。多線程
通常外面的解決方案是隻在一臺 APP 上裝 QUARTZ ,其它兩臺不裝,這樣集羣就形同虛設了;架構
另外一種解決方案是動代碼,這樣就要影響到原來已經寫好的 QUARTZ JOB 的代碼了,這對程序開發人員來講比較痛苦;oracle
本人仔細看了一下 Spring 的結構和 QUARTZ 的文檔,結合 Quartz 自身能夠實例化進數據的特性找到了相關的解決方案。
本方案優勢:
本人也事先搜索了一些資料,發覺全部目前在 GOOGLE 上或者在各大論壇裏提供的解決方案,要麼是隻解決了一部分,要麼是錯誤的,要麼是版本太老,要麼就是徹底抄別人的。
尤爲是在使用 QUARTZ+SPRING 對數據庫對象做實例化時會拋錯(源於 SPRING 的一個 BUG ),目前網上的解決方案所有是錯的或者乾脆沒說,本人在此方案中也會提出如何解決。
解決方案:
通常咱們的開發人員都喜歡使用 SPRING+QUARTZ ,所以這個 quartz.properties 都不用怎麼去寫,可是在集羣方案中 quartz.properties 必寫,若是不寫 quartz 會調用自身 jar 包中的 quartz.properties 做爲默認屬性文件,同時修改 quartz.xml 文件。
Quartz.xml 文件的內容 :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd ">
<beans>
<bean id="mapScheduler" lazy-init="false" autowire="no"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:quartz.properties" />
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
<!— 就是下面這句,由於該 bean 只能使用類反射來重構
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
</bean>
quartz.properties 文件的內容:
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_
org.quartz.jobStore.isClustered = true
org.quartz.dataSource.myXADS.jndiURL=jdbc/TestQuartzDS
org.quartz.dataSource.myXADS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP
org.quartz.dataSource.myXADS.java.naming.factory.initial = weblogic.jndi.WLInitialContextFactory
org.quartz.dataSource.myXADS.java.naming.provider.url = t3://localhost:7020
org.quartz.dataSource.myXADS.java.naming.security.principal = weblogic
org.quartz.dataSource.myXADS.java.naming.security.credentials = weblogic
緣由是在使用 quartz+spring 把 quartz 的 task 實例化進入數據庫時,會產生: serializable 的錯誤,緣由在於:
<bean id="jobtask" class="org.springframework.scheduling.quartz. MethodInvokingJobDetailFactoryBean ">
<property name="targetObject">
<ref bean="quartzJob"/>
</property>
<property name="targetMethod">
<value>execute</value>
</property>
</bean>
這個 MethodInvokingJobDetailFactoryBean 類中的 methodInvoking 方法,是不支持序列化的,所以在把 QUARTZ 的 TASK 序列化進入數據庫時就會拋錯。網上有說把 SPRING 源碼拿來,修改一下這個方案,而後再打包成 SPRING.jar 發佈,這些都是很差的方法,是不安全的。
必須根據 QuartzJobBean 來重寫一個本身的類,而後使用 SPRING 把這個重寫的類(咱們就名命它爲: MyDetailQuartzJobBean )注入 appContext 中後,再使用 AOP 技術反射出原有的 quartzJobx( 就是開發人員原來已經作好的用於執行 QUARTZ 的 JOB 的執行類 ) 。
下面來看 MyDetailQuartzJobBean 類:
public class MyDetailQuartzJobBean extends QuartzJobBean {
protected final Log logger = LogFactory.getLog(getClass());
private String targetObject;
private String targetMethod;
private ApplicationContext ctx;
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
try {
logger.info("execute [" + targetObject + "] at once>>>>>>");
Object otargetObject = ctx.getBean(targetObject);
Method m = null;
try {
m = otargetObject.getClass().getMethod(targetMethod,
new Class[] {});
m.invoke(otargetObject, new Object[] {});
} catch (SecurityException e) {
logger.error(e);
} catch (NoSuchMethodException e) {
logger.error(e);
}
} catch (Exception e) {
throw new JobExecutionException(e);
}
}
public void setApplicationContext(ApplicationContext applicationContext){
this.ctx=applicationContext;
}
public void setTargetObject(String targetObject) {
this.targetObject = targetObject;
}
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}
再來看完整的 quartz.xml (注意紅色加粗部分尤其重要):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd ">
<beans>
<bean id="mapScheduler" lazy-init="false" autowire="no"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:quartz.properties" />
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
<property name=" applicationContextSchedulerContextKey " value=" applicationContext " />
</bean>
<bean id="quartzJob" class="com.testcompany.framework.quartz.QuartzJob">
</bean>
<bean id="jobTask" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass">
<value>com.testcompany.framework.quartz. MyDetailQuartzJobBean </value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="quartzJob" value="quartzJob" />
<entry key="targetMethod" value="execute" />
</map>
</property>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="jobTask" />
</property>
<property name="cronExpression">
<value>0/5 * * * * ?</value>
</property>
</bean>
</beans>
測試:
幾個節點都帶有 quartz 任務,此時只有一臺 quartz 在運行,另幾個節點上的 quartz 沒有運行。
此時手動 shutdown 那臺運行 QUARTZ (在程序里加 system.out.println(「execute once…」), 運行 quartz 的那個節點在後臺會打印 execute once )的節點,過了 7 秒左右,另外一個節點的 quartz 自動監測到了集羣中運行着的 quartz 的 instance 已經 shutdown ,所以 quartz 集羣會自動把任一臺可用的 APP 上啓動起一個 quartz job 的任務。
自此, QUARTZ 使用 HA 策略的集羣大功告成,不用改原有代碼,配置一下咱們就可做到 QUARTZ 的集羣與自動錯誤冗餘。