springBoot-Quartz 定时任务
1.1 Quartz 概述
Quartz 是 OpenSymphony 開源組織在任務調度領域的一個開源項目,完全基于 Java 實現。該項目于 2009 年被 Terracotta 收購,目前是 Terracotta 旗下的一個項目。讀者可以到 http://www.quartz-scheduler.org/站點下載 Quartz 的發布版本及其源代碼。
1.2 Quartz特點
作為一個優秀的開源調度框架,Quartz 具有以下特點:
強大的調度功能,例如支持豐富多樣的調度方法,可以滿足各種常規及特殊需求;
靈活的應用方式,例如支持任務和調度的多種組合方式,支持調度數據的多種存儲方式;
分布式和集群能力,Terracotta 收購后在原來功能基礎上作了進一步提升。
另外,作為 Spring 默認的調度框架,Quartz 很容易與 Spring 集成實現靈活可配置的調度功能。
quartz調度核心元素:
- Scheduler:任務調度器,是實際執行任務調度的控制器。在spring中通過SchedulerFactoryBean封裝起來。
- Trigger:觸發器,用于定義任務調度的時間規則,有SimpleTrigger,CronTrigger,DateIntervalTrigger和NthIncludedDayTrigger,其中CronTrigger用的比較多,本文主要介紹這種方式。CronTrigger在spring中封裝在CronTriggerFactoryBean中。
- Calendar:它是一些日歷特定時間點的集合。一個trigger可以包含多個Calendar,以便排除或包含某些時間點。
- JobDetail:用來描述Job實現類及其它相關的靜態信息,如Job名字、關聯監聽器等信息。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean兩種實現,如果任務調度只需要執行某個類的某個方法,就可以通過MethodInvokingJobDetailFactoryBean來調用。
- Job:是一個接口,只有一個方法void execute(JobExecutionContext context),開發者實現該接口定義運行任務,JobExecutionContext類提供了調度上下文的各種信息。Job運行時的信息保存在JobDataMap實例中。實現Job接口的任務,默認是無狀態的,若要將Job設置成有狀態的,在quartz中是給實現的Job添加@DisallowConcurrentExecution注解(以前是實現StatefulJob接口,現在已被Deprecated),在與spring結合中可以在spring配置文件的job detail中配置concurrent參數。
1.3 Quartz 集群配置
quartz集群是通過數據庫表來感知其他的應用的,各個節點之間并沒有直接的通信。只有使用持久的JobStore才能完成Quartz集群。
數據庫表:以前有12張表,現在只有11張表,現在沒有存儲listener相關的表,多了QRTZ_SIMPROP_TRIGGERS表:
- 建表sql 點這里
如果沒有積分可以留言,我發給你哦。只是可能會比較晚
QRTZ_LOCKS就是Quartz集群實現同步機制的行鎖表,包括以下幾個鎖:CALENDAR_ACCESS 、JOB_ACCESS、MISFIRE_ACCESS 、STATE_ACCESS 、TRIGGER_ACCESS。
動手
記得要先新建數據庫,新建默認創建的11 張表哦
導入maven 依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</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><version>2.2.1</version></dependency><!--因為quartz 需要有Spring context 所有引入mail包--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId><version>3.2.0</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId></dependency>QuartzConfig
package com.abel.quartz.config;import java.beans.PropertyVetoException; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Properties;import com.zaxxer.hikari.HikariDataSource; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.Trigger; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.CronTriggerFactoryBean; import org.springframework.scheduling.quartz.JobDetailFactoryBean; import org.springframework.scheduling.quartz.SchedulerFactoryBean;/*** Created by yangyibo on 2019/1/16.*/ @Configuration public class QuartzConfig {/*** 1.通過name+group獲取唯一的jobKey;2.通過groupname來獲取其下的所有jobkey*/final static String GROUP_NAME = "QuartzJobGroups";@Value("${quartz.scheduler.instanceName}")private String quartzInstanceName;@Value("${spring.datasource.driverClassName}")private String myDSDriver;@Value("${spring.datasource.url}")private String myDSUrl;@Value("${spring.datasource.username}")private String myDSUser;@Value("${spring.datasource.password}")private String myDSPassword;@Value("${org.quartz.dataSource.myDS.maxConnections}")private int myDSMaxConnections;/*** 設置屬性** @return* @throws IOException*/private Properties quartzProperties() throws IOException {Properties prop = new Properties();// 調度標識名 集群中每一個實例都必須使用相同的名稱prop.put("quartz.scheduler.instanceName", quartzInstanceName);// ID設置為自動獲取 每一個必須不同prop.put("org.quartz.scheduler.instanceId", "AUTO");// 禁用quartz軟件更新prop.put("org.quartz.scheduler.skipUpdateCheck", "true");prop.put("org.quartz.scheduler.jmx.export", "true");// 數據庫代理類,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以滿足大部分數據庫prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");// 數據保存方式為數據庫持久化prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");// 數據庫別名 隨便取prop.put("org.quartz.jobStore.dataSource", "quartzDataSource");//prop.put("org.quartz.jobStore.dataSource", "myDS");// 表的前綴,默認QRTZ_prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");// 是否加入集群prop.put("org.quartz.jobStore.isClustered", "true");// 調度實例失效的檢查時間間隔prop.put("org.quartz.jobStore.clusterCheckinInterval", "20000");prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");// 信息保存時間 ms 默認值60秒prop.put("org.quartz.jobStore.misfireThreshold", "120000");prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE");// 程池的實現類(一般使用SimpleThreadPool即可滿足幾乎所有用戶的需求)prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");// 定線程數,至少為1(無默認值)(一般設置為1-100之間的整數合適)prop.put("org.quartz.threadPool.threadCount", "10");// 設置線程的優先級(最大為java.lang.Thread.MAX_PRIORITY 10,最小為Thread.MIN_PRIORITY 1,默認為5)prop.put("org.quartz.threadPool.threadPriority", "5");prop.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");prop.put("org.quartz.plugin.triggHistory.class", "org.quartz.plugins.history.LoggingJobHistoryPlugin");prop.put("org.quartz.plugin.shutdownhook.class", "org.quartz.plugins.management.ShutdownHookPlugin");prop.put("org.quartz.plugin.shutdownhook.cleanShutdown", "true");//#自定義連接池//org.quartz.dataSource.myDS.connectionProvider.class=com.poly.pay.schedule.DruidConnectionProviderreturn prop;}/*** 數據源** @return* @throws PropertyVetoException*/@Beanpublic HikariDataSource createDataSource() throws PropertyVetoException {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl(myDSUrl);dataSource.setDriverClassName(myDSDriver);dataSource.setUsername(myDSUser);dataSource.setPassword(myDSPassword);dataSource.setMaximumPoolSize(myDSMaxConnections);return dataSource;}/*** 創建觸發器工廠** @param jobDetail* @param cronExpression* @return*/private static CronTriggerFactoryBean cronTriggerFactoryBean(JobDetail jobDetail, String cronExpression) {CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();factoryBean.setJobDetail(jobDetail);factoryBean.setCronExpression(cronExpression);return factoryBean;}/****************************************************以下配置需要注意******************************************************//*** 調度工廠* 此處配置需要調度的觸發器 例如 executeJobTrigger** @param executeJobTrigger* @return* @throws IOException* @throws PropertyVetoException*/@Beanpublic SchedulerFactoryBean schedulerFactoryBean(@Qualifier("executeJobTrigger") Trigger executeJobTrigger) throws IOException, PropertyVetoException {SchedulerFactoryBean factory = new SchedulerFactoryBean();// this allows to update triggers in DB when updating settings in config file://用于quartz集群,QuartzScheduler 啟動時更新己存在的Job,這樣就不用每次修改targetObject后刪除qrtz_job_details表對應記錄了factory.setOverwriteExistingJobs(true);//用于quartz集群,加載quartz數據源//factory.setDataSource(dataSource);//QuartzScheduler 延時啟動,應用啟動完10秒后 QuartzScheduler 再啟動//factory.setStartupDelay(10);//用于quartz集群,加載quartz數據源配置factory.setAutoStartup(true);factory.setQuartzProperties(quartzProperties());factory.setApplicationContextSchedulerContextKey("applicationContext");factory.setDataSource(createDataSource());//注冊觸發器Trigger[] triggers = {executeJobTrigger};factory.setTriggers(triggers);return factory;}/*** 加載觸發器** 新建觸發器進行job 的調度 例如 executeJobDetail* @param jobDetail* @return*/@Bean(name = "executeJobTrigger")public CronTriggerFactoryBean executeJobTrigger(@Qualifier("executeJobDetail") JobDetail jobDetail) {//每天凌晨3點執行return cronTriggerFactoryBean(jobDetail, "0 1 0 * * ? ");}/*** 加載job** 新建job 類用來代理*** @return*/@Beanpublic JobDetailFactoryBean executeJobDetail() {return createJobDetail(InvokingJobDetailFactory.class, GROUP_NAME, "executeJob");}/*** 執行規則job工廠** 配置job 類中需要定時執行的 方法 execute* @param jobClass* @param groupName* @param targetObject* @return*/private static JobDetailFactoryBean createJobDetail(Class<? extends Job> jobClass,String groupName,String targetObject) {JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();factoryBean.setJobClass(jobClass);factoryBean.setDurability(true);factoryBean.setRequestsRecovery(true);factoryBean.setGroup(groupName);Map<String, String> map = new HashMap<>();map.put("targetMethod", "execute");map.put("targetObject", targetObject);factoryBean.setJobDataAsMap(map);return factoryBean;}}InvokingJobDetailFactory
package com.abel.quartz.config;import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.quartz.QuartzJobBean; import java.lang.reflect.Method;/*** Created by yangyibo on 2019/2/1.*/ public class InvokingJobDetailFactory extends QuartzJobBean {/*** 計劃任務所在類*/private String targetObject;/*** 具體需要執行的計劃任務*/private String targetMethod;private ApplicationContext ctx;@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {try {Object obj = ctx.getBean(targetObject);Method m = null;try {m = obj.getClass().getMethod(targetMethod);//調用被代理對象的方法m.invoke(obj);} catch (SecurityException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}} catch (Exception e) {throw new JobExecutionException(e);}}public void setApplicationContext(ApplicationContext applicationContext) {this.ctx = applicationContext;}public void setTargetObject(String targetObject) {this.targetObject = targetObject;}public void setTargetMethod(String targetMethod) {this.targetMethod = targetMethod;} }ExecuteJob
package com.abel.quartz.job;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service;/*** Created by yangyibo on 2019/2/1.*/ @Service public class ExecuteJob {private static final Logger logger = LoggerFactory.getLogger(ExecuteJob.class);/*** 方法名在quartz定義*/public void execute() {System.out.println("定時任務執行了。。。。。");} }application.properties
## tomcat配置 server.port=8090 #server.tomcat.maxHttpHeaderSize=8192 server.tomcat.uri-encoding=UTF-8 spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true spring.http.encoding.force=true spring.messages.encoding=UTF-8 # tomcat最大線程數,默認為200 server.tomcat.max-threads=800 # session最大超時時間(分鐘),默認為30 server.session-timeout=60## spring 配置 spring.application.name=springboot-Quartz application.main=com.abel.quartz.Application## 主數據源,默認的 spring.datasource.url=jdbc:mysql://localhost:3306/quart?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&useSSL=false spring.datasource.username=root spring.datasource.password=admin spring.datasource.driverClassName=com.mysql.jdbc.Driver## 連接池配置 spring.datasource.type=com.zaxxer.hikari.HikariDataSource #最小空閑連接 spring.datasource.hikari.minimum-idle=10 #連接池中允許的最大連接數。缺省值:10;推薦的公式:((core_count * 2) + effective_spindle_count) spring.datasource.hikari.maximum-pool-size=30 #spring.datasource.hikari.auto-commit=true #一個連接idle狀態的最大時長(毫秒),超時則被釋放(retired),缺省:10分鐘。minimumIdle<maximumPoolSize時生效 spring.datasource.hikari.idle-timeout=120000 #自定義連接池名 #spring.datasource.hikari.pool-name=DatebookHikariCP #一個連接的生命時長(毫秒),超時而且沒被使用則被釋放(retired),缺省:30分鐘,建議設置比數據庫超時時長少30秒,參考MySQL wait_timeout參數(show variables like '%timeout%';) spring.datasource.hikari.max-lifetime=1800000 #等待連接池分配連接的最大時長(毫秒),超過這個時長還沒可用的連接則發生SQLException, 缺省:30秒 spring.datasource.hikari.connection-timeout=30000 #指定驗證連接有效性的超時時間,默認是5秒 spring.datasource.hikari.validation-timeout=3000 spring.datasource.hikari.connection-test-query=SELECT 1# 調度標識名 集群中每一個實例都必須使用相同的名稱 quartz.scheduler.instanceName=QuartScheduler # 允許最大連接 org.quartz.dataSource.myDS.maxConnections=10源代碼地址:https://github.com/527515025/springBoot/tree/master/springboot-Quartz
參考資料:https://blog.csdn.net/lkl_csdn/article/details/73613033
總結
以上是生活随笔為你收集整理的springBoot-Quartz 定时任务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 辞职时被领导挽留,要不要留下?
- 下一篇: Java中反射机制入门