前面的博文中提到的quartz集羣方式會有如下缺點:java
1.假設配置了3個定時任務,job1,job2,job3,這時數據庫裏會有3條job相關的記錄,若是下次上線要停掉一個定時任務job1,那即便定時任務配置文件 quartz.xml 中的trigger 去掉了,數據庫仍是會有該條記錄,若代碼沒有去掉,那定時任務仍是會執行。spring
------解決方法1:修改該trigger的觸發時間,好比年改爲2099,讓它不觸發,(SchedulerFactoryBean中配置了 <property name="overwriteExistingJobs" value="true" />),會覆蓋數據庫中的相關記錄。數據庫
缺點:曲線救國策略,不夠好。app
------解決方法2:刪除數據庫中該job的相關記錄。jvm
缺點:要操做數據庫,並且表中數據還有外鍵約束,操做起來麻煩,還可能不當心破壞了其餘job信息。ui
解決方法3:this
介紹一種用配置文件的方式,經過開關控制,達到更細粒度的控制。主要是經過Scheduler的相關方法:Trigger和JobDetail能夠註冊到Scheduler中,二者在Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查找定位容器中某一對象的依據。Scheduler定義了多個接口方法,容許外部經過組及名稱訪問和控制容器中Trigger和JobDetail。Scheduler包含了不少方法,添加、執行一次、恢復所有、刪掉任務暫停所有等方法,方便用戶靈活使用。spa
1.建立定時任務的配置文件 quartz-config.propertiescode
#定時任務配置文件
#key:
# CronTriggerFactoryBean配置的id
# switch:定時任務開關,ON:開啓該定時任務; OFF:關閉該定時任務
# cron:該定時任務的執行時間間隔(cron表達式)xml
#定時任務(每隔15分鐘執行)
intrBillTrigger.switch=OFF
intrBillTrigger.cron=0 0/15 * * * ?
2.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-3.0.xsd"> <bean id="intrBillJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="com.cmcc.open.task.utils.MyDetailQuartzJobBean"></property> //參考之前的那篇博文 <property name="Durability" value="true"/> <property name="jobDataAsMap"> <map> <entry key="targetObject" value="billTimerIntrTask" /> <entry key="targetMethod" value="intrBillTimer" /> </map> </property> </bean> <bean id="intrBillTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="intrBillJob"></property> <!--<property name="cronExpression" value="0 0/15 * * * ?"></property>--> <property name="cronExpression" value="${intrBillTrigger.cron}"></property> </bean> <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource" ref="dataSourceQuartz"></property> <property name="jobFactory" ref="customJobFactory"></property> <property name="applicationContextSchedulerContextKey" value="applicationContext"></property> <property name="configLocation" value="classpath:quartz.properties" /> //參考之前那篇博文 <property name="overwriteExistingJobs" value="true" /> <property name="triggers"> <list> <ref bean="intrBillTrigger"/> </list> </property> </bean> </beans>
3.QuartzScheduleConfigTask 定時任務控制類
package com.cmcc.open.task.service; import com.cmcc.open.framework.utils.EncryptPropertyPlaceholderConfigurer; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.stereotype.Component; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; /** * QuartzScheduleConfigTask. * 初始化定時任務配置 */ @Component public class QuartzScheduleConfigTask { /** The Constant log. */ private static Logger logger = LoggerFactory.getLogger(QuartzScheduleConfigTask.class); private static Properties properties; @Autowired private SchedulerFactoryBean schedulerFactoryBean; /** * 初始化定時任務配置 */ public void initQuartzConfig() { String triggerName = null; //讀取定時任務配置文件(包括開關、執行時間) Properties properties = getProperty(); if (properties == null) { logger.warn(".......cannot find quartz-config properties file"); } try { //解析定時任務配置文件,進行修改 Set<Map.Entry<Object, Object>> entrySet = properties.entrySet(); Scheduler sched = schedulerFactoryBean.getScheduler(); //先對讀取到的配置進行過濾,將開關和cron放到同一個key中 Iterator it1 = entrySet.iterator(); while (it1.hasNext()) { Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) it1.next(); String key = String.valueOf(entry.getKey()); if (!key.endsWith("switch")) { continue; } triggerName = key.substring(0, key.lastIndexOf(".")); if ("OFF".equalsIgnoreCase(String.valueOf(entry.getValue()))) { //關閉該定時任務 closeQuartz(sched, triggerName); } else { //獲取該定時任務的執行時間,與配置文件進行比較 //目前定時任務的實現時間從配置文件中讀取,不須要再進行修改 String cron = null; Iterator it2 = entrySet.iterator(); while (it2.hasNext()) { Map.Entry<Object, Object> entryChild = (Map.Entry<Object, Object>) it2.next(); if(!String.valueOf(entryChild.getKey()).startsWith(triggerName) || !String.valueOf(entryChild.getKey()).endsWith("cron")) { continue; } cron = String.valueOf(entryChild.getValue()); it1.remove(); break; } updateQuartz(sched, triggerName, cron); } } } catch (Exception e) { logger.warn("Exception in trigger:" + triggerName); logger.warn("Exception in initQuartzConfig:" + e.getMessage(), e); } } /** * 刪除定時任務 * @param sched * @param triggerName * @throws Exception */ private void closeQuartz(Scheduler sched, String triggerName) throws Exception { TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, Scheduler.DEFAULT_GROUP); CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey); if (trigger == null) { logger.warn("no trigger get for key:" + triggerName + " do nothing for this trigger"); return; } //若是定時任務關閉,則從數據庫中移除 //中止觸發器 sched.pauseTrigger(triggerKey); //移除觸發器 sched.unscheduleJob(triggerKey); //刪除任務 String jobName = trigger.getJobKey().getName(); sched.deleteJob(JobKey.jobKey(jobName, Scheduler.DEFAULT_GROUP)); logger.info("delete quartz job:" + jobName + " succ........."); } /** * 更新定時任務執行時間 * @param sched * @param triggerName * @param cron * @throws Exception */ private void updateQuartz(Scheduler sched, String triggerName, String cron) throws Exception { TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, Scheduler.DEFAULT_GROUP); CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey); //若是定時任務開啓,從數據庫讀取該job的執行時間進行比較 //若是相關不作任何處理,若是不等則更新數據庫中的執行時間 String oldTime = trigger.getCronExpression(); if (!oldTime.equals(cron)) { logger.info("quartz job's oldTime not equals config cron, trigger name is : " + triggerName + "|oldTime is : " + oldTime + "|config cron is : " + cron); //觸發器 TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger(); //觸發其名,觸發器組 triggerBuilder.withIdentity(triggerName, Scheduler.DEFAULT_GROUP); triggerBuilder.startNow(); //觸發器時間設定 triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron)); //建立Trigger對象 trigger = (CronTrigger) triggerBuilder.build(); //修改一個任務的觸發時間 sched.rescheduleJob(triggerKey, trigger); logger.info("update quartz job:" + trigger.getJobKey().getName() + " succ........."); } else { logger.info("the quartz job's config cron is equals old cron, do nothing for this quartz: " + triggerName); } } /** * 讀取配置文件 * @return */ private Properties getProperty() { try { // 先獲取jvm傳參 String pathRoot = "D:\\Test\\"; String path = pathRoot + "quartz-config.properties"; if ("".equals(pathRoot)){ path = QuartzScheduleConfigTask.class.getClassLoader().getResource("quartz-config.properties").getPath(); } Resource resource = new FileSystemResource(path); properties = PropertiesLoaderUtils.loadProperties(resource); Set<Map.Entry<Object, Object>> entrySet = properties.entrySet(); for (Map.Entry<Object, Object> entry : entrySet) { String decryptValue = EncryptPropertyPlaceholderConfigurer .getDecryptPara(entry.getKey().toString(), entry.getValue().toString()); properties.setProperty(entry.getKey().toString(), decryptValue); } return properties; } catch (Exception e) { logger.error(e.getMessage(), e); } return null; } }