Quertz是一個開源的做業任務調度框架,他能夠完成像JavaScript定時器類式的功能,其實Java中Timer也可實現部分功能,但相比Quertz仍是略遜一籌,本人此次須要解決的就是按期統計消費記錄的功能。你還能夠用他完成按期執行各種操做的功能。好比html
這些問題總結起來就是:java
在某一個有規律的時間點幹某件事。而且時間的觸發的條件能夠很是複雜(好比每個月最後一個工做日的17:50),複雜到須要一個專門的框架來幹這個事。spring
Quartz就是來幹這樣的事,你給它一個觸發條件的定義,它負責到了時間點,觸發相應的Job起來幹活。數據庫
採用Spring整合Quartz使用代碼方式或者xml方式均可以,我這裏也提供兩種方式,名稱相同適合對比學習。設計模式
1:引入配置,pom.xml文件引入下列兩個路徑(非Maven可自行配置)api
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.1.0.RELEASE</version> </dependency>
2:建立【com.xqc.campusshop.config.quartz】包進行相關配置服務器
(我知道你們不喜歡看源碼,可是我仍是得說看源碼效果好)源碼中併發
productSellDailyService爲按期統計消費記錄Service接口 框架
dailyCalculate 爲ProductSellDailyService接口中執行按期統計的的方法,ssh
triggerFactory.setCronExpression("? 0 0 * * ? *");爲定時的時間,可訪問在線cron表達式生成器生成相應時間 http://cron.qqe2.com/
1 package com.xqc.campusshop.config.quartz; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.context.annotation.Bean; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.scheduling.quartz.CronTriggerFactoryBean; 7 import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean; 8 import org.springframework.scheduling.quartz.SchedulerFactoryBean; 9 10 import com.xqc.campusshop.service.ProductSellDailyService; 11 12 @Configuration 13 public class QuartzConfiguration { 14 15 //按期統計消費記錄Service接口 16 @Autowired 17 private ProductSellDailyService productSellDailyService; 18 19 @Autowired 20 private MethodInvokingJobDetailFactoryBean jobDetailFactory; 21 22 @Autowired 23 private CronTriggerFactoryBean productSellDailyTriggerFactory; 24 25 /** 26 * 建立jobDetail並返回 27 * @return 28 */ 29 @Bean(name="jobDetailFactory") 30 public MethodInvokingJobDetailFactoryBean crateJobDetail(){ 31 32 //new出jobDetailFactory對象,此工廠主要用來製做一個jobDetail,及製做一個任務 33 //因爲咱們所作的定時任務根本上講其實就是執行一個方法,因此這個工廠比較方便 34 MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean(); 35 //設置jobDetail的名字 36 jobDetailFactoryBean.setName("product_sell_daily_job"); 37 //設置jobDetail的組名 38 jobDetailFactoryBean.setGroup("job_product_sell_daily_group"); 39 //對於相同的JobDetail,當指定多個Triggger時,極可能第一個job完成之前,第二個job就開始了 40 //指定設爲false,多個job則不會併發運行,第二個job不會再第一個job完成前開始 41 jobDetailFactoryBean.setConcurrent(false); 42 //指定運行任務的類 43 jobDetailFactoryBean.setTargetObject(productSellDailyService); 44 //指定運行任務的方法 45 jobDetailFactoryBean.setTargetMethod("dailyCalculate"); 46 47 return jobDetailFactoryBean; 48 } 49 50 /** 51 * 建立cronTriggerFactory並返回 52 * 53 * @return 54 */ 55 @Bean("productSellDailyTriggerFactory") 56 public CronTriggerFactoryBean createProductSellDailyTrigger(){ 57 //建立TriggerFactory實例,用來建立trigger 58 CronTriggerFactoryBean triggerFactory = new CronTriggerFactoryBean(); 59 //設置triggerFactory的名字 60 triggerFactory.setName("product_sell_daily_trigger"); 61 //設置組名 62 triggerFactory.setGroup("job_product_sell_daily_group"); 63 //綁定jobDetail 64 triggerFactory.setJobDetail(jobDetailFactory.getObject()); 65 //設置cron表達式,請訪問:http://cron.qqe2.com/在線表達式生成器 66 triggerFactory.setCronExpression("? 0 0 * * ? *"); 67 68 return triggerFactory; 69 70 } 71 /** 72 * 建立調度工廠並返回 73 * @return 74 */ 75 @Bean("schedulerFactory") 76 public SchedulerFactoryBean createSchedulerFactory(){ 77 78 SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); 79 schedulerFactory.setTriggers(productSellDailyTriggerFactory.getObject()); 80 return schedulerFactory; 81 82 } 83 84 85 }
3:業務方法我就不貼了,你們能夠打印一下測試一下便可(記得把cron表達式時間改小一點),譬如
1 package com.xqc.campusshop.service.impl; 2 3 import org.springframework.stereotype.Service; 4 5 @Service 6 public class ProductSellDailyServiceImpl implements ProductSellDailyService{ 7 8 @Override 9 public void dailyCalculate() { 10 system.out.println("Quartz跑起來了!」); 11 12 } 13 }
1:引入pom.xml(同上)
2:在Spring配置文件配置以下
<!-- 使用MethodInvokingJobDetailFactoryBean,任務類能夠不實現Job接口,經過targetMethod指定調用方法--> <bean id="productSellDailyService" class="com.xqc.campusshop.service"/> <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="group" value="job_product_sell_daily_group"/> <property name="name" value="product_sell_daily_job"/> <!--false表示等上一個任務執行完後再開啓新的任務--> <property name="concurrent" value="false"/> <property name="targetObject"> <ref bean="productSellDailyService"/> </property> <property name="targetMethod"> <value>dailyCalculate</value> </property> </bean> <!-- 調度觸發器 --> <bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="name" value="product_sell_daily_trigger"/> <property name="group" value="job_product_sell_daily_group"/> <property name="jobDetail"> <ref bean="jobDetail" /> </property> <property name="cronExpression"> <value>? 0 0 * * ? *</value> </property> </bean> <!-- 調度工廠 --> <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="myTrigger"/> </list> </property> </bean>
3:編寫業務方法(同上)
官網:http://www.quartz-scheduler.org/
API:http://www.quartz-scheduler.org/api/2.2.1/index.html
Scheduler:調度器。全部的調度都是由它控制。
Trigger: 定義觸發的條件。
Job & JobDetail: JobDetail 定義的是任務數據,而真正的執行邏輯是在Job中,爲何設計成 JobDetail + Job,不直接使用Job?這是由於任務是有可能併發執行,若是Scheduler直接使用Job,就會存在對同一個Job實例併發訪問的問題。而JobDetail & Job 方式,sheduler每次執行,都會根據JobDetail建立一個新的Job實例,這樣就能夠規避併發訪問的問題。
核心關係圖
Job
Job實例在Quartz中的生命週期
每次調度器執行Job時,它在調用execute方法前會建立一個新的Job實例
當調用完成後,關聯的Job對象實例會被釋放,釋放實例會被垃圾回收機制回收。
JobBuild
JonDetail
JobDetail爲Job實例提供了許多設置屬性,以及JobDataMap成員變量屬性,它用來存儲特定Job實例信息,能夠理解爲Job攜帶的內容。調度器須要藉助JobDetail對象來添加Job實例
JobDetail和Trigger都有name和group。
name是它們在這個sheduler裏面的惟一標識。若是咱們要更新一個JobDetail定義,只須要設置一個name相同的JobDetail實例便可。
group是一個組織單元,sheduler會提供一些對整組操做的API,好比 scheduler.resumeJobs()。
JobExecutionContext
當Scheduler調用一個Job,就會將JobExecutionContext傳遞給Job的execute()方法
Job能經過JobExecutionContext對象訪問到Quartz運行時候的環境以及Job自己的明細數據
JobDataMap
在進行任務調度時JobDataMap存儲,在JobExecutionContext中,很是方便獲取
JobDataMap能夠用來裝載任何可序列化的數據對象,當Job實例對象被執行時這些參數對象會傳遞給他
JobDataMap實現了JDK的Map接口,而且添加了一些很是方便的方法用來存取基本的數據類型
獲取JobDataMap的兩種方式
從Map中直接獲取
Job實現類中添加Setter方法對應JobDataMap的鍵值(Quartz框架默認的JobFactory實現類在初始化Job實例對象時就會自動調用這些setter方法)
Trigger
startTime和endTime指定的Trigger會被觸發的時間區間。在這個區間以外,Trigger是不會被觸發的。
Trigger的實現類
SimpleTrigger
指定從某一個時間開始,以必定的時間間隔(單位是毫秒)執行的任務。
它適合的任務相似於:9:00 開始,每隔1小時,執行一次。
它的屬性有:
repeatInterval 重複間隔
repeatCount 重複次數。實際執行次數是 repeatCount+1。由於在startTime的時候必定會執行一次。
CronTrigger
適合於更復雜的任務,它支持類型於Linux Cron的語法(而且更強大)。基本上它覆蓋了以上三個Trigger的絕大部分能力(但不是所有)—— 固然,也更難理解。
它適合的任務相似於:天天0:00,9:00,18:00各執行一次。
它的屬性只有:
星號():可用在全部字段中,表示對應時間域的每個時刻,例如, 在分鐘字段時,表示「每分鐘」;
問號(?):該字符只在日期和星期字段中使用,它一般指定爲「無心義的值」,至關於點位符;
減號(-):表達一個範圍,如在小時字段中使用「10-12」,則表示從10到12點,即10,11,12;
逗號(,):表達一個列表值,如在星期字段中使用「MON,WED,FRI」,則表示星期一,星期三和星期五;
斜槓(/):x/y表達一個等步長序列,x爲起始值,y爲增量步長值。如在分鐘字段中使用0/15,則表示爲0,15,30和45秒,而5/15在分鐘字段中表示5,20,35,50,你也可使用*/y,它等同於0/y;
L:該字符只在日期和星期字段中使用,表明「Last」的意思,但它在兩個字段中意思不一樣。L在日期字段中,表示這個月份的最後一天,如一月的31號,非閏年二月的28號;若是L用在星期中,則表示星期六,等同於7。可是,若是L出如今星期字段裏,並且在前面有一個數值X,則表示「這個月的最後X天」,例如,6L表示該月的最後星期五;
W:該字符只能出如今日期字段裏,是對前導日期的修飾,表示離該日期最近的工做日。例如15W表示離該月15號最近的工做日,若是該月15號是星期六,則匹配14號星期五;若是15日是星期日,則匹配16號星期一;若是15號是星期二,那結果就是15號星期二。但必須注意關聯的匹配日期不可以跨月,如你指定1W,若是1號是星期六,結果匹配的是3號星期一,而非上個月最後的那天。W字符串只能指定單一日期,而不能指定日期範圍;
LW組合:在日期字段能夠組合使用LW,它的意思是當月的最後一個工做日;
井號(#):該字符只能在星期字段中使用,表示當月某個工做日。如6#3表示當月的第三個星期五(6表示星期五,#3表示當前的第三個),而4#5表示當月的第五個星期三,假設當月沒有第五個星期三,忽略不觸發;
C:該字符只在日期和星期字段中使用,表明「Calendar」的意思。它的意思是計劃所關聯的日期,若是日期沒有被關聯,則至關於日曆中全部日期。例如5C在日期字段中就至關於日曆5日之後的第一天。1C在星期字段中至關於星期往後的第一天。
JobStore
Quartz支持任務持久化,這可讓你在運行時增長任務或者對現存的任務進行修改,併爲後續任務的執行持久化這些變動和增長的部分。中心概念是JobStore接口。默認的是RAMJobStore。
ThreadTool
TriggerBuild
Scheduler
Calendar
Quartz體貼地爲咱們提供如下幾種Calendar,注意,全部的Calendar既能夠是排除,也能夠是包含,取決於:
HolidayCalendar。指定特定的日期,好比20140613。精度到天。
DailyCalendar。指定天天的時間段(rangeStartingTime, rangeEndingTime),格式是HH:MM[:SS[:mmm]]。也就是最大精度能夠到毫秒。
WeeklyCalendar。指定每星期的星期幾,可選值好比爲java.util.Calendar.SUNDAY。精度是天。
MonthlyCalendar。指定每個月的幾號。可選值爲1-31。精度是天
AnnualCalendar。 指定每一年的哪一天。使用方式如上例。精度是天。
CronCalendar。指定Cron表達式。精度取決於Cron表達式,也就是最大精度能夠到秒。
一個Trigger能夠和多個Calendar關聯,以排除或包含某些時間點
監聽器:
JobListener,TriggerListener,SchedulerListener
經過工廠建立一個Scheduler
建立一個實現Job接口的實現類(就是要具體作的事情,能夠具體調用本身寫的service)
定義一個Job,並綁定咱們本身實現Job接口的實現類(例如經過JobBuilder的方式)
建立Trigger,並設置相關參數,如啓動時間等。
將job和trigger綁定到scheduler對象上,並啓動
代碼略(網上一百度都有的HelloQuartz,我就懶得寫了)
quartz定時調度是經過Object.wait方式(native方法)實現的,其本質是經過操做系統的時鐘來實現的。
Bulild模式
組件模式
Factory模式
鏈式寫法
在 Quartz 中,有兩類線程,Scheduler 調度線程和任務執行線程,其中任務執行線程一般使用一個線程池維護一組線程。
Scheduler 調度線程主要有兩個: 執行常規調度的線程,和執行 misfired trigger 的線程。常規調度線程輪詢存儲的全部 trigger,若是有須要觸發的 trigger,即到達了下一次觸發的時間,則從任務執行線程池獲取一個空閒線程,執行與該 trigger 關聯的任務。Misfire 線程是掃描全部的 trigger,查看是否有 misfired trigger,若是有的話根據 misfire 的策略分別處理。下圖描述了這兩個線程的基本流程:
若quartz是配置在spring中,當服務器啓動時,就會裝載相關的bean。SchedulerFactoryBean實現了InitializingBean接口,所以在初始化bean的時候,會執行afterPropertiesSet方法,該方法將會調用SchedulerFactory(DirectSchedulerFactory 或者 StdSchedulerFactory,一般用StdSchedulerFactory)建立Scheduler。SchedulerFactory在建立quartzScheduler的過程當中,將會讀取配置參數,初始化各個組件
quartz集羣是經過數據庫表來感知其餘的應用的,各個節點之間並無直接的通訊。只有使用持久的JobStore才能完成Quartz集羣。
數據庫表:之前有12張表,如今只有11張表,如今沒有存儲listener相關的表,多了QRTZ_SIMPROP_TRIGGERS表:
QRTZ_LOCKS就是Quartz集羣實現同步機制的行鎖表,包括如下幾個鎖:CALENDAR_ACCESS 、JOB_ACCESS、MISFIRE_ACCESS 、STATE_ACCESS 、TRIGGER_ACCESS。
(篇幅有點長了,其餘的之後再出吧!)