1.增長QuartzUtils工具類java
2.採用優先級的ShutDownHook進行Scheduler和Job的關閉。git
3.加入Quartz插件Plugin支持。LogPlugin和ShutdownHookPlugin.程序員
4.新增MxBean堅持Java程序和任務狀態。例如內存監控,死鎖監控。github
5.已打包依賴到github倉庫。 倉庫地址:https://github.com/huangyueranbbc/QuartzUtils-1.0.0-RELEASE安全
pom增長配置:ide
<repositories> <repository> <id>QuartzUtils</id> <name>QuartzUtils</name> <url>https://raw.github.com/huangyueranbbc/QuartzUtils-1.0.0-RELEASE/master/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <dependency> <groupId>com.hyr.quartz.utils</groupId> <artifactId>QuartzUtils</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
一、支持延遲定時任務。 二、簡化調用Quartz過程。一行代碼搞定。 QuartzUtils.scheduleWithFixedDelay(scheduler, MyJob.class, 0, 1, TimeUnit.SECONDS, -1, "ProducerJob", "QUARTZ-JOB-GROUP"); 三、新增具備優先級的ShutdownHook進行Quartz組件的順序安全關閉。 四、新增MXBean進行任務過程監測。 五、優化Quartz Plugin插件。提供Job/Trigger日誌、ShutDownHook插件。 六、提供優化的Listener監聽器。 能夠定製修改。 七、支持日誌級別設置。 八、提供測試入口。
核心代碼只有一行工具
QuartzUtils.scheduleWithFixedDelay(scheduler, MyJob.class, 0, 1, TimeUnit.SECONDS, -1, "ProducerJob", "QUARTZ-JOB-GROUP");
/******************************************************************************* * @date 2018-11-06 下午 2:20 * @author: <a href=mailto:huangyr>黃躍然</a> * @Description: ******************************************************************************/ public class QuartzUtilsTest { public static void main(String[] args) throws SchedulerException { StdSchedulerFactory schedulerFactory1 = QuartzUtils.getStdSchedulerFactory(2, Thread.NORM_PRIORITY, "UPLOAD_JOB1", "UPLOAD_JOB1"); Scheduler scheduler = schedulerFactory1.getScheduler(); StdSchedulerFactory schedulerFactory2 = QuartzUtils.getStdSchedulerFactory(2, Thread.NORM_PRIORITY, "UPLOAD_JOB2", "UPLOAD_JOB2"); Scheduler scheduler2 = schedulerFactory2.getScheduler(); QuartzUtils.addSchedulerShutdownHook(scheduler); QuartzUtils.addSchedulerShutdownHook(scheduler2); QuartzUtils.startLogPlugin(scheduler, QuartzUtils.LOG_INFO); // 啓動日誌插件 QuartzUtils.startShutDownHookPlugin(scheduler); // 啓動ShutDownHook插件 QuartzUtils.startLogPlugin(scheduler2, QuartzUtils.LOG_DEBUG); // 啓動日誌插件 QuartzUtils.startShutDownHookPlugin(scheduler2); // 啓動ShutDownHook插件 // 綁定單個Listener監聽器 QuartzUtils.bindSchedulerListenerManager(scheduler, new DefaultSchedulerListener("DefaultSchedulerListener"), new DefaultJobListener("DefaultJobListener"), new DefaultTriggerListener("DefaultTriggerListener")); QuartzUtils.bindSchedulerListenerManager(scheduler2, new DefaultSchedulerListener("DefaultSchedulerListener"), new DefaultJobListener("DefaultJobListener"), new DefaultTriggerListener("DefaultTriggerListener")); // 綁定多個Listener監聽器 List<SchedulerListener> schedulerListeners = new ArrayList<>(); for (int i = 0; i < 10; i++) { schedulerListeners.add(new DefaultSchedulerListener("SchedulerListener--" + i)); } List<JobListener> jobListeners = new ArrayList<>(); for (int i = 0; i < 10; i++) { jobListeners.add(new DefaultJobListener("JobListener--" + i)); } List<TriggerListener> triggerListeners = new ArrayList<>(); for (int i = 0; i < 10; i++) { triggerListeners.add(new DefaultTriggerListener("TriggerListener--" + i)); } // QuartzUtils.bindSchedulerListenerManagers(scheduler, schedulerListeners, jobListeners, triggerListeners); // 注入屬性Map JobDataMap dataMap = new JobDataMap(); dataMap.put("jobDesc", "job desc."); QuartzUtils.scheduleWithFixedDelay(scheduler, MyJob.class, 0, 1, TimeUnit.SECONDS, -1, "ProducerJob", "QUARTZ-JOB-GROUP"); // 注入屬性 //QuartzUtils.scheduleWithFixedDelay(scheduler2, MyJob.class, 0, 2, TimeUnit.SECONDS, -1, "ProducerJobData1", "QUARTZ-JOB-GROUP", dataMap); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //System.exit(0); } }
/******************************************************************************* * @date 2018-11-13 下午 5:53 * @author: <a href=mailto:huangyr>黃躍然</a> * @Description: Quartz工具類 ******************************************************************************/ public class QuartzUtils { private static Logger log = LoggerFactory.getLogger(QuartzUtils.class); private static ShutdownHookManager shutdownHookManager = ShutdownHookManager.get(); // shutdownhook // QuartzLoggingPlugin 日誌級別 @SuppressWarnings("WeakerAccess") public final static int LOG_TRACE = 0; @SuppressWarnings("WeakerAccess") public final static int LOG_DEBUG = 10; @SuppressWarnings("WeakerAccess") public final static int LOG_INFO = 20; @SuppressWarnings("WeakerAccess") public final static int LOG_WARN = 30; @SuppressWarnings("WeakerAccess") public final static int LOG_ERROR = 40; /** * @param threadCount 線程數 * @param threadPriority 線程優先級 5默認優先級 * @param threadNamePrefix 工做線程池中線程名稱的前綴將被附加前綴 * @param schedulerName 實例名稱 * @return * @throws SchedulerException */ public static StdSchedulerFactory getStdSchedulerFactory(int threadCount, int threadPriority, String threadNamePrefix, String schedulerName) throws SchedulerException { Properties props = new Properties(); props.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); props.setProperty("org.quartz.threadPool.threadCount", String.valueOf(threadCount)); // 線程數 props.setProperty("org.quartz.threadPool.threadPriority", String.valueOf(threadPriority)); // 線程優先級 5默認優先級 props.setProperty("org.quartz.threadPool.threadNamePrefix", threadNamePrefix); // 工做線程池中線程名稱的前綴將被附加前綴 props.setProperty("org.quartz.scheduler.instanceName", schedulerName); // 實例名稱 props.setProperty("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore"); // 將job數據保存在ram,性能最高。但程序崩潰,job調度數據會丟失。 props.setProperty("org.quartz.scheduler.skipUpdateCheck","true"); return new StdSchedulerFactory(props); } public static StdSchedulerFactory getStdSchedulerFactory(Properties props) throws SchedulerException { return new StdSchedulerFactory(props); } public static StdSchedulerFactory getStdSchedulerFactory(String schedulerName) throws SchedulerException { Properties props = new Properties(); props.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); props.setProperty("org.quartz.threadPool.threadCount", "1"); // 線程數 props.setProperty("org.quartz.threadPool.threadPriority", String.valueOf(Thread.NORM_PRIORITY)); // 線程優先級 5默認優先級 props.setProperty("org.quartz.threadPool.threadNamePrefix", schedulerName); // 工做線程池中線程名稱的前綴將被附加前綴 props.setProperty("org.quartz.scheduler.instanceName", schedulerName); // 實例名稱 props.setProperty("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore"); // 將job數據保存在ram,性能最高。但程序崩潰,job調度數據會丟失。 props.setProperty("org.quartz.scheduler.skipUpdateCheck","true"); return new StdSchedulerFactory(props); } /** * 綁定監聽器 * * @param scheduler * @param schedulerListener * @param jobListener * @param triggerListener * @throws SchedulerException */ public static void bindSchedulerListenerManager(Scheduler scheduler, SchedulerListener schedulerListener, JobListener jobListener, TriggerListener triggerListener) throws SchedulerException { ListenerManager listenerManager = scheduler.getListenerManager(); if (schedulerListener != null) { listenerManager.addSchedulerListener(schedulerListener); } if (jobListener != null) { listenerManager.addJobListener(jobListener); } if (triggerListener != null) { listenerManager.addTriggerListener(triggerListener); } } /** * 綁定多個監聽器 * * @param scheduler * @param schedulerListeners * @param jobListeners * @param triggerListeners * @throws SchedulerException */ public static void bindSchedulerListenerManagers(Scheduler scheduler, Collection<SchedulerListener> schedulerListeners, Collection<JobListener> jobListeners, Collection<TriggerListener> triggerListeners) throws SchedulerException { ListenerManager listenerManager = scheduler.getListenerManager(); if (schedulerListeners != null && !schedulerListeners.isEmpty()) { for (SchedulerListener schedulerListener : schedulerListeners) { listenerManager.addSchedulerListener(schedulerListener); } } if (jobListeners != null && !jobListeners.isEmpty()) { for (JobListener jobListener : jobListeners) { listenerManager.addJobListener(jobListener); } } if (triggerListeners != null && !triggerListeners.isEmpty()) { for (TriggerListener triggerListener : triggerListeners) { listenerManager.addTriggerListener(triggerListener); } } } /** * 延時啓動 * * @param scheduler 調度器 * @param job JobClass * @param initialDelay 首次延時啓動時間 * @param timeUnit 時間單位 * @param timer cron表達式 * @param jobName 任務名稱 * @param groupName 組名 */ public static void scheduleWithFixedDelayByCron(Scheduler scheduler, Class<? extends Job> job, long initialDelay, TimeUnit timeUnit, String timer, String jobName, String groupName) { try { long delayMillis = timeUnit.toMillis(initialDelay); // 延時啓動時間 JobDetail jobDetail = getJobDetail(job, jobName, groupName); long tmpTime = System.currentTimeMillis() + delayMillis; //延遲啓動任務時間 Date statTime = new Date(tmpTime); // 啓動時間 Trigger trigger = getTrigger(timer, jobName, groupName, statTime); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); log.info("job:{} is start. timer:{}", jobName, timer); } catch (Exception e) { log.error("add job error. jobName:{}, timer:{}", jobName, timer, e); } } /** * 延時啓動 * * @param scheduler 調度器 * @param job JobClass * @param initialDelay 首次延時啓動時間 * @param timeUnit 時間單位 * @param timer cron表達式 * @param jobName 任務名稱 * @param groupName 組名 * @param dataMap 屬性注入 以JavaBean的形式注入,須要該屬性名和set方法 */ public static void scheduleWithFixedDelayByCron(Scheduler scheduler, Class<? extends Job> job, long initialDelay, TimeUnit timeUnit, String timer, String jobName, String groupName, JobDataMap dataMap) { try { long delayMillis = timeUnit.toMillis(initialDelay); // 延時啓動時間 JobDetail jobDetail = getJobDetailBindData(job, jobName, groupName, dataMap); long tmpTime = System.currentTimeMillis() + delayMillis; //延遲啓動任務時間 Date statTime = new Date(tmpTime); // 啓動時間 Trigger trigger = getTrigger(timer, jobName, groupName, statTime); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); log.info("job:{} is start. timer:{}", jobName, timer); } catch (Exception e) { log.error("add job error. jobName:{}, timer:{}", jobName, timer, e); } } /** * @param scheduler 調度器 * @param job JobClass * @param initialDelay 首次延時啓動時間 * @param timeUnit 時間單位 * @param delay 間隔時間 * @param repeatCount 重複執行次數 -1無限次數 0不執行 * @param jobName 任務名稱 * @param groupName 組名 */ public static void scheduleWithFixedDelay(Scheduler scheduler, Class<? extends Job> job, long initialDelay, long delay, TimeUnit timeUnit, int repeatCount, String jobName, String groupName) { long intervalTime = timeUnit.toMillis(delay); try { addJobShutdownHook(scheduler, jobName, groupName); long delayMillis = timeUnit.toMillis(initialDelay); // 延時啓動時間 JobDetail jobDetail = getJobDetail(job, jobName, groupName); long tmpTime = System.currentTimeMillis() + delayMillis; //延遲啓動任務時間 Date statTime = new Date(tmpTime); // 啓動時間 Trigger trigger = getSimpleTrigger(intervalTime, jobName, groupName, repeatCount, statTime); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); log.info("job:{} is start. delay:{}", jobName, delay); } catch (Exception e) { log.error("add job error. jobName:{}, intervalTime:{}", jobName, intervalTime, e); } } /** * @param scheduler 調度器 * @param job JobClass * @param initialDelay 首次延時啓動時間 * @param timeUnit 時間單位 * @param delay 間隔時間 * @param repeatCount 重複執行次數 -1無限次數 0不執行 * @param jobName 任務名稱 * @param groupName 組名 * @param dataMap 屬性注入 以JavaBean的形式注入,須要該屬性名和set方法 */ public static void scheduleWithFixedDelay(Scheduler scheduler, Class<? extends Job> job, long initialDelay, long delay, TimeUnit timeUnit, int repeatCount, String jobName, String groupName, JobDataMap dataMap) { long intervalTime = timeUnit.toMillis(delay); try { addJobShutdownHook(scheduler, jobName, groupName); long delayMillis = timeUnit.toMillis(initialDelay); // 延時啓動時間 JobDetail jobDetail = getJobDetailBindData(job, jobName, groupName, dataMap); long tmpTime = System.currentTimeMillis() + delayMillis; //延遲啓動任務時間 Date statTime = new Date(tmpTime); // 啓動時間 Trigger trigger = getSimpleTrigger(intervalTime, jobName, groupName, repeatCount, statTime); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); log.info("job:{} is start. delay:{}", jobName, delay); } catch (Exception e) { log.error("add job error. jobName:{}, intervalTime:{}", jobName, intervalTime, e); } } private static Trigger getSimpleTrigger(long delay, String jobName, String groupName, int repeatCount, Date statTime) { SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule(); simpleScheduleBuilder.withIntervalInMilliseconds(delay).withRepeatCount(repeatCount); simpleScheduleBuilder.withMisfireHandlingInstructionFireNow(); // 以當前時間爲觸發頻率當即觸發執行 return TriggerBuilder.newTrigger() .withPriority(Trigger.DEFAULT_PRIORITY) // 優先級 .withIdentity("Trigger-" + jobName, "Trigger-" + groupName) .startAt(statTime) //默認當前時間啓動 .withSchedule(simpleScheduleBuilder) //兩秒執行一次 .build(); } /** * 給trigger下全部job注入屬性 * * @param delay 間隔時間 * @param repeatCount 重複執行次數 -1無限次數 0不執行 * @param jobName 任務名稱 * @param groupName 組名 * @param statTime 開始執行時間 * @param dataMap 屬性注入 以JavaBean的形式注入,須要該屬性名和set方法 * @return */ private static Trigger getSimpleTriggerBindData(long delay, String jobName, String groupName, int repeatCount, Date statTime, JobDataMap dataMap) { SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule(); simpleScheduleBuilder.withIntervalInMilliseconds(delay).withRepeatCount(repeatCount); simpleScheduleBuilder.withMisfireHandlingInstructionFireNow(); // 以當前時間爲觸發頻率當即觸發執行 return TriggerBuilder.newTrigger() .withPriority(Trigger.DEFAULT_PRIORITY) // 優先級 .withIdentity("Trigger-" + jobName, "Trigger-" + groupName) .startAt(statTime) //默認當前時間啓動 .withSchedule(simpleScheduleBuilder) //兩秒執行一次 .usingJobData(dataMap) // 輸入屬性 .build(); } private static Trigger getTrigger(String timer, String jobName, String groupName, Date statTime) { CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(timer); cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); // 默認 以當前時間爲觸發頻率馬上觸發一次執行,而後按照Cron頻率依次執行.會合並部分的misfire,正常執行下一個週期的任務. // cronScheduleBuilder.withMisfireHandlingInstructionDoNothing(); // 全部的misfire無論,執行下一個週期的任務) // cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); //全部misfire的任務會立刻執行 return TriggerBuilder.newTrigger() .withPriority(Trigger.DEFAULT_PRIORITY) // 優先級 .withIdentity("Trigger-" + jobName, "Trigger-" + groupName) .startAt(statTime) //默認當前時間啓動 .withSchedule(cronScheduleBuilder) //兩秒執行一次 .build(); } /** * 給trigger下全部的Job注入屬性 * * @param timer cron表達式 * @param jobName 任務名稱 * @param groupName 組名 * @param statTime 開始執行時間 * @param dataMap 屬性注入 以JavaBean的形式注入,須要該屬性名和set方法 * @return */ private static Trigger getTriggerBindData(String timer, String jobName, String groupName, Date statTime, JobDataMap dataMap) { CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(timer); cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); // 默認 以當前時間爲觸發頻率馬上觸發一次執行,而後按照Cron頻率依次執行.會合並部分的misfire,正常執行下一個週期的任務. // cronScheduleBuilder.withMisfireHandlingInstructionDoNothing(); // 全部的misfire無論,執行下一個週期的任務) // cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); //全部misfire的任務會立刻執行 return TriggerBuilder.newTrigger() .withPriority(Trigger.DEFAULT_PRIORITY) // 優先級 .withIdentity("Trigger-" + jobName, "Trigger-" + groupName) .startAt(statTime) //默認當前時間啓動 .withSchedule(cronScheduleBuilder) //兩秒執行一次 .usingJobData(dataMap) // 注入屬性 .build(); } private static JobDetail getJobDetail(Class<? extends Job> job, String jobName, String groupName) { return JobBuilder.newJob(job) .storeDurably(true) // 若是一個job是非持久的,當沒有活躍的trigger與之關聯的時候,會被自動地從scheduler中刪除 .requestRecovery(true) // job可恢復。scheduler發生硬關閉(hard shutdown)(好比運行的進程崩潰了,或者關機了),則當scheduler從新啓動的時候,該job會被從新執行。 .withIdentity(jobName, groupName) //job 的name和group .build(); } /** * @param job 任務對象 * @param jobName 任務名稱 * @param groupName 組名 * @param dataMap 屬性注入 以JavaBean的形式注入,須要該屬性名和set方法 * @return */ private static JobDetail getJobDetailBindData(Class<? extends Job> job, String jobName, String groupName, JobDataMap dataMap) { return JobBuilder.newJob(job) .storeDurably(true) // 若是一個job是非持久的,當沒有活躍的trigger與之關聯的時候,會被自動地從scheduler中刪除 .requestRecovery(true) // job可恢復。scheduler發生硬關閉(hard shutdown)(好比運行的進程崩潰了,或者關機了),則當scheduler從新啓動的時候,該job會被從新執行。 .withIdentity(jobName, groupName) //job 的name和group .usingJobData(dataMap) // 屬性注入 .build(); } /** * 刪除定時Job * * @param scheduler * @param jobName * @param groupName * @throws SchedulerException */ public static void removeJob(Scheduler scheduler, String jobName, String groupName) throws SchedulerException { scheduler.deleteJob(JobKey.jobKey(jobName, groupName)); } /** * 啓動日誌插件 * * @param scheduler * @param log_level 日誌統一打印級別 */ public static void startLogPlugin(Scheduler scheduler, int log_level) { try { String schedulerName = scheduler.getSchedulerName(); // trigger log plugin QuartzLoggingTriggerHistoryPlugin triggerLogPlugin = new QuartzLoggingTriggerHistoryPlugin(); triggerLogPlugin.initialize(schedulerName, scheduler, new SimpleClassLoadHelper()); triggerLogPlugin.setLog_level(log_level); // job log plugin QuartzLoggingJobHistoryPlugin jobLogPlugin = new QuartzLoggingJobHistoryPlugin(); jobLogPlugin.initialize(schedulerName, scheduler, new SimpleClassLoadHelper()); jobLogPlugin.setLog_level(log_level); addPluginShutdownHook(triggerLogPlugin); addPluginShutdownHook(jobLogPlugin); triggerLogPlugin.start(); jobLogPlugin.start(); } catch (SchedulerException e) { log.error("start log plugin error.", e); } } /** * 啓動ShutDownHook插件 * * @param scheduler */ public static void startShutDownHookPlugin(Scheduler scheduler) { try { String schedulerName = scheduler.getSchedulerName(); QuartzShutdownHookPlugin shutdownHookPlugin = new QuartzShutdownHookPlugin(); shutdownHookPlugin.initialize(schedulerName, scheduler, new SimpleClassLoadHelper()); addPluginShutdownHook(shutdownHookPlugin); shutdownHookPlugin.start(); } catch (Exception e) { log.error("start shutdown hook plugin error.", e); } } /** * plugin shutdownhook * * @param schedulerPlugin */ public static void addPluginShutdownHook(final SchedulerPlugin schedulerPlugin) { Thread schedulerShutdownHook = new Thread() { @Override public void run() { if (schedulerPlugin != null) { synchronized (schedulerPlugin) { schedulerPlugin.shutdown(); log.info("scheduler plugin:{} shutdown success.", schedulerPlugin.getClass().getSimpleName()); } } } }; shutdownHookManager.addShutdownHook(schedulerShutdownHook, HookPriority.PLUGIN_PRIORITY.value()); } /** * Job ShutdownHook * * @param scheduler * @param jobName * @param groupName */ public static void addJobShutdownHook(final Scheduler scheduler, final String jobName, final String groupName) { Thread quartzJobShutdownHook = new Thread() { @Override public void run() { try { if (scheduler != null) { synchronized (scheduler) { if (!scheduler.isShutdown()) { removeJob(scheduler, jobName, groupName); } log.info("job:{} remove success.", jobName); } } } catch (Exception e) { log.error("delete job error. jobName:{}, groupName:{}", jobName, groupName, e); } } }; // 注:該優先級要比Schedule的Hook優先級高 shutdownHookManager.addShutdownHook(quartzJobShutdownHook, HookPriority.JOB_PRIORITY.value()); } /** * Scheduler ShutdownHook * 避免和Quartz自帶ShutdownPlugin插件同時使用 * * @param scheduler */ public static void addSchedulerShutdownHook(final Scheduler scheduler) { Thread schedulerShutdownHook = new Thread() { @Override public void run() { try { if (scheduler != null) { synchronized (scheduler) { String schedulerName = scheduler.getSchedulerName(); if (!scheduler.isShutdown()) { scheduler.shutdown(true); } log.info("scheduler shutdown success. scheduler:{}", schedulerName); } } } catch (Exception e) { log.error("shutdown scheduler error.", e); } } }; // 注:該優先級要比Job的Hook優先級低 shutdownHookManager.addShutdownHook(schedulerShutdownHook, HookPriority.SCHEDULER_PRIORITY.value()); } }