背景
最近在做項目,項目中有個需求:需要使用定時任務,這個定時任務需要即時生效。
查看Quartz官網之后發現:Quartz提供兩種基本作業存儲類型:
- RAMJobStore :RAM也就是內存,默認情況下Quartz會將任務調度存在內存中,這種方式性能是最好的,因為內存的速度是最快的。不好的地方就是數據缺乏持久性,但程序崩潰或者重新發布的時候,所有運行信息都會丟失
- JDBC作業存儲:存到數據庫之后,可以做單點也可以做集群,當任務多了之后,可以統一進行管理。關閉或者重啟服務器,運行的信息都不會丟失。缺點就是運行速度快慢取決於連接數據庫的快慢。
SpringBoot集成Quartz
我們也可以自己去將quartz和springBoot整合在一起,其實說是springBoot還不如說是sping,因為我們沒有用到spirngboot的相關的快捷方式。
如果童鞋們想快速集成Quartz,立刻看到效果的話,可以直接往下翻,直接看SpirngBoot自帶的Quartz插件。但我建議大家還是從spring整合Quartz開始,懂的原理,方有收獲。
Quartz初始化表
如果需要做持久化的話,數據肯定是要存在數據庫的,那么到底存在哪些表呢?其實官網文檔也跟我們講過了,地址如下:
http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-09.html
其中有句話:
JDBCJobStore works with nearly any database, it has been used widely with Oracle, PostgreSQL, MySQL, MS SQLServer, HSQLDB, and DB2. To use JDBCJobStore, you must first create a set of database tables for Quartz to use. You can find table-creation SQL scripts in the “docs/dbTables” directory of the Quartz distribution.
大概就是支持這么多的數據庫類型。如果你要使用JDBCJoBStore的話,你先要創建一些表,這些表在 “doc/dbTables”里面。“doc/dbTables” 在哪兒呢?其實都在源碼里面,直接到官網下下來就行了。
Spring整合Quartz
pom文件
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <!-- <version>2.3.0</version> --> </dependency> <!--定時任務需要依賴context模塊--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!--<!– druid數據庫連接池 –>--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> </dependencies>
對應的properties 文件
#使用自己的配置文件 org.quartz.jobStore.useProperties:true #默認或是自己改名字都行 org.quartz.scheduler.instanceName: DefaultQuartzScheduler #如果使用集群,instanceId必須唯一,設置成AUTO org.quartz.scheduler.instanceId = AUTO org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true #存儲方式使用JobStoreTX,也就是數據庫 org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate #是否使用集群(如果項目只部署到 一台服務器,就不用了) org.quartz.jobStore.isClustered = false org.quartz.jobStore.clusterCheckinInterval=20000 org.quartz.jobStore.tablePrefix = qrtz_ org.quartz.jobStore.dataSource = myDS #配置數據源 #數據庫中quartz表的表名前綴 org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/aipyun?serverTimezone=GMT&characterEncoding=utf-8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = root123 org.quartz.dataSource.myDS.maxConnections = 5
核心QuartzConfiguration類:
ackage com.cj.config; import org.quartz.Scheduler; import org.quartz.spi.JobFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.quartz.SchedulerFactoryBean; /** * 描述 : quartz 配置信息 * * @author caojing * @create 2018-12-24-16:47 */ @Configuration public class QuartzConfiguration { @Autowired private JobFactory jobFactory; @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setJobFactory(jobFactory); // 用於quartz集群,QuartzScheduler 啟動時更新己存在的Job schedulerFactoryBean.setOverwriteExistingJobs(true); //延長啟動 schedulerFactoryBean.setStartupDelay(1); //設置加載的配置文件 schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties")); return schedulerFactoryBean; } @Bean public Scheduler scheduler() { return schedulerFactoryBean().getScheduler(); } }
這其中我們把2個類的初始化移到了IOC中,因為之前Quartz的實例化是自己去控制的,為什么要這么做后面會有講到。
一個是SchedulerFactoryBean類,這個類其實就是之前xml配置中的SchedulerFactoryBean。附上之前xml配置如下(這里不需要配置,springboot建議我們少用xml配置)
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="oceanStatusCronTrigger"/> </list> </property> </bean>
這個類我相信只要用過xml配置的人一定很熟悉,這是Quartz入口。同時也是spring 和Scheduler 關系的橋梁。以便在Spring容器啟動后,Scheduler自動開始工作,而在Spring容器關閉前,自動關閉Scheduler。
JobFactory類
package com.cj.config; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.scheduling.quartz.AdaptableJobFactory; import org.springframework.scheduling.quartz.SpringBeanJobFactory; import org.springframework.stereotype.Component; /** * 描述: * * @author caojing * @create 2018-12-26-14:03 */ @Component public class JobFactory extends AdaptableJobFactory { @Autowired private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { // 調用父類的方法 Object jobInstance = super.createJobInstance(bundle); // 進行注入 capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }
這個類的作用就是講Job的實例化交給IOC去進行。
其實問題在於:
Job對象的實例化過程是在Quartz中進行的,注入的實體類是在Spring容器當中的 所以在job中無法注入Srping容器的實體類。
如何納入:Job的創建都是通過JobFactory創建的。JobFactory 有2個實現類:AdaptableJobFactory 和 SimpleJobFactory:
- 自定義的工廠類 JobFactory 繼承 AdaptableJobFactory 。
- 通過調用父類 AdaptableJobFactory 的方法createJobInstance來實現對Job的實例化。
- 在Job實例化完以后,再調用自身方法為創建好的Job實例進行屬性自動裝配並將其納入到Spring容器的管理之中。(通過AutowireCapableBeanFactory納入)。
UploadTask 類:
package com.cj.quartzdemo; import com.cj.controller.IndexController; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.QuartzJobBean; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.util.Date; /** * 描述: * * @author caojing * @create 2018-12-25-11:38 */ @Component @DisallowConcurrentExecution public class UploadTask extends QuartzJobBean { @Autowired private IndexController indexController; @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println(new Date() + "任務開始------------------------------------"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(new Date() + "任務結束------------------------------------"); } }
繼承QuartzJobBean類,重寫executeInternal方法。
附:DisallowConcurrentExecution 比如job執行10秒,任務是每隔5秒執行,加上這個注解,程序就會等10秒結束后再執行下一個任務。
indexController類:
package com.cj.controller; import com.cj.quartzdemo.UploadTask; import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * 描述: * * @author caojing * @create 2018-12-26-14:11 */ @Controller public class IndexController { @Autowired private Scheduler scheduler; @RequestMapping(value = "/index", method = RequestMethod.GET) public void index() throws SchedulerException { //cron表達式 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/8 * * * * ?"); //根據name 和group獲取當前trgger 的身份 TriggerKey triggerKey = TriggerKey.triggerKey("cj", "123"); CronTrigger triggerOld = null; try { //獲取 觸發器的信息 triggerOld = (CronTrigger) scheduler.getTrigger(triggerKey); } catch (SchedulerException e) { e.printStackTrace(); } if (triggerOld == null) { //將job加入到jobDetail中 JobDetail jobDetail = JobBuilder.newJob(UploadTask.class).withIdentity("cj", "123").build(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("cj","123").withSchedule(cronScheduleBuilder).build(); //執行任務 scheduler.scheduleJob(jobDetail, trigger); } else { System.out.println("當前job已存在--------------------------------------------"); } } }
瀏覽器輸入 http://localhost:8080/index 就可以看到數據庫已經存儲了我們寫的cron表達式和相應的類。
查看數據庫表(qrtz_cron_triggers)附上截圖:

至此,job 已經被我們成功持久化到數據庫。我們來回顧下整體的一個流程。
pom文件添加對應的依賴。
mysql數據庫對應表的初始化。
配置對應的properties
將原來quartz控制的類的實例化交給spirng IOC控制。(對應的是核心QuartzConfiguration類和JobFactory類)
業務邏輯層對job進行控制。
