在開發過程中,需要實現定時來執行某些方法任務,這時可以使用Quartz框架來實現這個功能。
一 Quartz簡單使用
Quartz中主要包含幾個核心概念,如下:
- Job 表示一個工作,要執行的具體內容。此接口中只有一個方法,如下:
void execute(JobExecutionContext context)- JobDetail 表示一個具體的可執行的調度程序,Job 是這個可執行程調度程序所要執行的內容,另外 JobDetail 還包含了這個任務調度的方案和策略。
- Trigger 代表一個調度參數的配置,什么時候去調。
- Scheduler 代表一個調度容器,一個調度容器中可以注冊多個 JobDetail 和 Trigger。當 Trigger 與 JobDetail 組合,就可以被 Scheduler 容器調度了。
1.1 配置Scheduler
上文說Scheduler是一個調度容器,任意一個JobDetail和任意一個Trigger結合為一對即可進行注冊,而Scheduler需要實例化,只有在實例化以后,才能執行他的啟動(start)、暫停(stand-by)、停止(shutdown)方法。
注意:scheduler被停止后,除非重新實例化,否則不能重新啟動;只有當scheduler啟動后,即使處於暫停狀態也不行,trigger才會被觸發(job才會被執行)。
1 SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); 2 3 Scheduler sched = schedFact.getScheduler(); 4 5 sched.start(); 6 7 // define the job and tie it to our HelloJob class 8 JobDetail job = newJob(HelloJob.class) 9 .withIdentity("myJob", "group1") 10 .build(); 11 12 // Trigger the job to run now, and then every 40 seconds 13 Trigger trigger = newTrigger() 14 .withIdentity("myTrigger", "group1") 15 .startNow() 16 .withSchedule(simpleSchedule() 17 .withIntervalInSeconds(40) //每40s執行一次 18 .repeatForever()) 19 .build(); 20 21 // Tell quartz to schedule the job using our trigger 22 sched.scheduleJob(job, trigger);
1.2 實現jobDetail
具體的工作類需要實現接口Job,接口需要實現一個execute方法,而具體的job要執行的任務,就寫在execute方法中。如下:
public class HelloJob implements Job { public HelloJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { System.err.println("Hello! HelloJob is executing."); } }
二 Quartz和Spring boot結合
創建一個監聽類來實現配置Scheduler,使Spring boot在啟動時自動加載該類,開始定時任務。如下:
1 @Component 2 public class TimedRegister implements ApplicationListener<ApplicationReadyEvent> { 3 4 @Autowired 5 FindMessage findMessage; 6 7 @Override 8 public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { 9 try { 10 // Grab the Scheduler instance from the Factory 11 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); 12 13 // and start it off 14 scheduler.start(); 15 16 // define the job and tie it to our HelloJob class 17 JobDetail mysqlDoJob = JobBuilder.newJob(MysqlTimedSendJob.class) 18 .withIdentity("job1", "jobGroup1") 19 .build(); 20 21 mysqlDoJob.getJobDataMap().put("findMessage", findMessage); 22 23 Trigger mysqlTrigger = newTrigger() 24 .withIdentity("trigger1", "triggerGroup1") 25 .withSchedule(CronScheduleBuilder.cronSchedule("0 0 10 * * ?")) 26 .build(); 27 28 scheduler.scheduleJob(mysqlDoJob, mysqlTrigger); 29 30 } catch (Exception e) { 31 logger.error("TimedRegister daily job error", e); 32 } 33 } 34 }
以上需要注意幾點:
1. ApplicationListener接口中需要傳入監聽參數(ApplicationReadyEvent),因為如果不傳入參數的話,會對每個event都進行監聽,則會發生同時執行好幾個定時任務這樣的慘狀。這個問題不只存在與定時任務的監聽。
2. 先看一段解釋:
我們傳給scheduler一個JobDetail實例,因為我們在創建JobDetail時,將要執行的job的類名傳給了JobDetail,所以scheduler就知道了要執行何種類型的job;每次當scheduler執行job時,在調用其execute(…)方法之前會創建該類的一個新的實例;執行完畢,對該實例的引用就被丟棄了,實例會被垃圾回收;這種執行策略帶來的一個后果是,job必須有一個無參的構造函數(當使用默認的JobFactory時);另一個后果是,在job類中,不應該定義有狀態的數據屬性,因為在job的多次執行中,這些屬性的值不會保留。那么如何給job實例增加屬性或配置呢?如何在job的多次執行中,跟蹤job的狀態呢?答案就是:JobDataMap,JobDetail對象的一部分。
因為job的具體類不是Spring創建的,而是quartz創建的,所以不能通過注入的方式來調用findmessage,只能通過將這個findmessage創建對象后通過參數注入的方式由JobDataMap來傳入Job具體的實現中。所以我們在jobdetail中添加參數。
mysqlDoJob.getJobDataMap().put("findMessage", findMessage);
然后在job的具體方法中,通過getdetail然后getjobdatamap的方式來獲取具體的findMessage方法,從而實現定時執行一個方法的操作。
1 public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { 2 3 FindMessage findMessage = (FindMessage) jobExecutionContext.getJobDetail() 4 .getJobDataMap().get("findMessage"); 5 logger.info("get findMessage instance success"); 6 7 findMessage.findDayMessage(); 8 logger.info("send ding from mysql is success"); 9 }
三 Quartz中的JobDataMap使用指南
JobDataMap中可以包含不限量的(序列化的)數據對象,在job實例執行的時候,可以使用其中的數據;JobDataMap是Java Map接口的一個實現,額外增加了一些便於存取基本類型的數據的方法。
將job加入到scheduler之前,在構建JobDetail時,可以將數據放入JobDataMap。
所以可以在添加JobDetail時添加jobDataMap,如下:
1 // define the job and tie it to our DumbJob class 2 JobDetail job = newJob(DumbJob.class) 3 .withIdentity("myJob", "group1") // name "myJob", group "group1" 4 .usingJobData("jobSays", "Hello World!") 5 .usingJobData("myFloatValue", 3.141f) 6 .build();
然后在job的具體類中將其取出,如下:
1 public class DumbJob implements Job { 2 3 public DumbJob() { 4 } 5 6 public void execute(JobExecutionContext context) 7 throws JobExecutionException 8 { 9 JobKey key = context.getJobDetail().getKey(); 10 11 JobDataMap dataMap = context.getJobDetail().getJobDataMap(); 12 13 String jobSays = dataMap.getString("jobSays"); 14 float myFloatValue = dataMap.getFloat("myFloatValue"); 15 16 System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue); 17 } 18 }