SpringBoot定時任務(wù)(schedule、quartz)
原文鏈接:https://www.cnblogs.com/jing99/p/11546559.html
Scheduled
只適合處理簡單的計劃任務(wù),不能處理分布式計劃任務(wù)。優(yōu)勢:是spring框架提供的計劃任務(wù),開發(fā)簡單,執(zhí)行效率比較高。且在計劃任務(wù)數(shù)量太多的時候,可能出現(xiàn)阻塞,崩潰,延遲啟動等問題。
Scheduled定時任務(wù)是spring3.0版本之后自帶的一個定時任務(wù)。其所屬Spring的資源包為:spring-context-support。所以需要使用Scheduled定時任務(wù)機(jī)制時,需要在工程中依賴對應(yīng)資源,具體如下:
<dependency>
??<groupId>org.springframeworkgroupId>
??<artifactId>spring-context-supportartifactId>
dependency>如果在spring應(yīng)用中需要啟用Scheduled定時任務(wù),則需要在啟動類上增加注解@EnableScheduling,代表啟用Scheduled定時任務(wù)機(jī)制。具體如下:
@SpringBootApplication
@EnableScheduling
public class AppStarter {
??public?static?void?main(String[] args) {
????SpringApplication.run(AppStarter.class, args);
??}
}Scheduled定時任務(wù)的核心在于注解@Scheduled,這個注解的核心屬性是cron,代表定時任務(wù)的觸發(fā)計劃表達(dá)式。這個表達(dá)式的格式為:
@Scheduled(cron="seconds?minutes hours day month week")或
@Scheduled(cron="seconds?minutes hours day month week year")推薦使用第一種表達(dá)式形式,因為在很多其他技術(shù)中都有不同的定時任務(wù)機(jī)制,其中用于設(shè)置觸發(fā)計劃的表達(dá)式都是第一種cron表達(dá)式。第二種表達(dá)式不能說是Spring Scheduled特有的,也是只有少數(shù)技術(shù)支持的。
cron表達(dá)式中,每個位置的約束如下:

星號(*):可用在所有字段中,表示對應(yīng)時間域的每一個時刻,例如,*在分鐘字段時,表示“每分鐘”;
問號(?):該字符只在日期和星期字段中使用,它通常指定為“無意義的值”,相當(dāng)于占位符;
減號(-):表達(dá)一個范圍,如在小時字段中使用“10-12”,則表示從10到12點,即10,11,12;
逗號(,):表達(dá)一個列表值,如在星期字段中使用“MON,WED,FRI”,則表示星期一,星期三和星期五;
斜杠(/):x/y表達(dá)一個等步長序列,x為起始值,y為增量步長值。如在秒數(shù)字段中使用0/15,則表示為0,15,30和45秒,而5/15在分鐘字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;
L:該字符只在日期和星期字段中使用,代表“Last”的意思,但它在兩個字段中意思不同。L在日期字段中,表示這個月份的最后一天,如一月的31號,非閏年二月的28號;如果L用在星期中,則表示星期六,等同于7。但是,如果L出現(xiàn)在星期字段里,而且在前面有一個數(shù)值X,則表示“這個月的最后X天”,例如,6L表示該月的最后星期五;
W:該字符只能出現(xiàn)在日期字段里,是對前導(dǎo)日期的修飾,表示離該日期最近的工作日。例如15W表示離該月15號最近的工作日,如果該月15號是星期六,則匹配14號星期五;如果15日是星期日,則匹配16號星期一;如果15號是星期二,那結(jié)果就是15號星期二。但必須注意關(guān)聯(lián)的匹配日期不能夠跨月,如你指定1W,如果1號是星期六,結(jié)果匹配的是3號星期一,而非上個月最后的那天。W字符串只能指定單一日期,而不能指定日期范圍;
LW組合:在日期字段可以組合使用LW,它的意思是當(dāng)月的最后一個工作日;
井號(#):該字符只能在星期字段中使用,表示當(dāng)月某個工作日。如6#3表示當(dāng)月的第三個星期五(6表示星期五,#3表示當(dāng)前的第三個),而4#5表示當(dāng)月的第五個星期三,假設(shè)當(dāng)月沒有第五個星期三,忽略不觸發(fā);
C:該字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是計劃所關(guān)聯(lián)的日期,如果日期沒有被關(guān)聯(lián),則相當(dāng)于日歷中所有日期。例如5C在日期字段中就相當(dāng)于日歷5日以后的第一天。1C在星期字段中相當(dāng)于星期日后的第一天。
Cron表達(dá)式對特殊字符的大小寫不敏感,對代表星期的縮寫英文大小寫也不敏感。
計劃任務(wù)Scheduled是通過一個線程池實現(xiàn)的。是一個多線程的調(diào)度。SpringBoot會初始化一個線程池,線程池默認(rèn)大小為1,專門用于執(zhí)行計劃任務(wù)。每個計劃任務(wù)啟動的時候,都從線程池中獲取一個線程執(zhí)行,如果發(fā)生異常,只是執(zhí)行當(dāng)前任務(wù)的線程發(fā)生異常,而不是計劃任務(wù)調(diào)度線程發(fā)生異常。如果當(dāng)前定時任務(wù)還未執(zhí)行完成,當(dāng)相同的定時任務(wù)又進(jìn)入到執(zhí)行周期時,不會觸發(fā)新的定時任務(wù)。如:
@Scheduled(cron="* * * * * ?")
public?void?test1(){
????Random r = new?Random();
????/*int i = r.nextInt(100);
????if(i % 3 == 0){
????????throw new RuntimeException("error");
????}*/
????System.out.println(Thread.currentThread().getName() + " cron=* * * * * ? --- "?+ new?Date());
????try{
????????Thread.sleep(2000);
????}catch(Exception e){
????????e.printStackTrace();
????}
}如結(jié)果所示(每次的線程名稱一致,由于前一個定時任務(wù)未執(zhí)行完成,因此造成后一個任務(wù)的推遲,而不是1秒執(zhí)行一次,而是3秒):
pool-1-thread-1 cron=* *?* *?* ? --- Thu Sep 19 02:23:20 CST 2019
pool-1-thread-1 cron=* *?* *?* ? --- Thu Sep 19 02:23:23 CST 2019
pool-1-thread-1 cron=* *?* *?* ? --- Thu Sep 19 02:23:26 CST 2019
pool-1-thread-1 cron=* *?* *?* ? --- Thu Sep 19 02:23:29 CST 2019quartz
Quartz是OpenSymphony開源組織在Job scheduling領(lǐng)域又一個開源項目,它可以與J2EE與J2SE應(yīng)用程序相結(jié)合也可以單獨使用。Quartz可以用來創(chuàng)建簡單或為運(yùn)行十個,百個,甚至是好幾萬個Jobs這樣復(fù)雜的程序。
Quartz是一個完全由java編寫的開源作業(yè)調(diào)度框架。不要讓作業(yè)調(diào)度這個術(shù)語嚇著你。盡管Quartz框架整合了許多額外功能, 但就其簡易形式看,你會發(fā)現(xiàn)它易用得簡直讓人受不了!
在開發(fā)Quartz相關(guān)應(yīng)用時,只要定義了Job(任務(wù)),Trigger(觸發(fā)器)和Scheduler(調(diào)度器),即可實現(xiàn)一個定時調(diào)度能力。其中Scheduler是Quartz中的核心,Scheduler負(fù)責(zé)管理Quartz應(yīng)用運(yùn)行時環(huán)境,Scheduler不是靠自己完成所有的工作,是根據(jù)Trigger的觸發(fā)標(biāo)準(zhǔn),調(diào)用Job中的任務(wù)執(zhí)行邏輯,來完成完整的定時任務(wù)調(diào)度。
Job - 定時任務(wù)內(nèi)容是什么。
Trigger - 在什么時間上執(zhí)行job。
Scheduler - 維護(hù)定時任務(wù)環(huán)境,并讓觸發(fā)器生效。
在SpringBoot中應(yīng)用Quartz,需要依賴下述資源:
????????
????????<dependency>
????????????<groupId>org.springframeworkgroupId>
????????????<artifactId>spring-context-supportartifactId>
????????dependency>
????????
????????<dependency>
????????????<groupId>org.quartz-schedulergroupId>
????????????<artifactId>quartzartifactId>
????????????<version>2.2.1version>
????????????
????????????<exclusions>
????????????????<exclusion>
????????????????????<artifactId>slf4j-apiartifactId>
????????????????????<groupId>org.slf4jgroupId>
????????????????exclusion>
????????????exclusions>
????????dependency>
????????
????????<dependency>
????????????<groupId>org.springframeworkgroupId>
????????????<artifactId>spring-txartifactId>
????????dependency>
dependencies>啟動器添加注解@EnableScheduling:
/**
?* @EnableScheduling 必要
?* 開啟定時任務(wù)機(jī)制。
?*/
@SpringBootApplication
@EnableScheduling
public class AppStarter {
????public?static?void?main(String[] args) {
????????SpringApplication.run(AppStarter.class, args);
????}
}定義JOB任務(wù)以及JOB任務(wù)調(diào)用的模擬業(yè)務(wù)對象:
public?class?SpringBootQuartzJobDemo?implements?Job?{
????// 用于模擬任務(wù)中的業(yè)務(wù)對象。也可能是數(shù)據(jù)訪問對象,或其他類型的對象。
????@Autowired
????private?CommonsUtil4Quartz commonsUtil4Quartz;
????
????@Override
????public?void?execute(JobExecutionContext context)?throws?JobExecutionException {
????????System.out.println("SpringBootQuartzJobDemo : "?+ new?Date());
????????this.commonsUtil4Quartz.testMethod();
????}
}@Component
public?class?CommonsUtil4Quartz?{
????public?void?testMethod(){
????????System.out.println("CommonsUtil4Quartz testMethod run...");
????}
}創(chuàng)建Trigger以及JobDetail對象,并用Schedule配置定時任務(wù):
/**
?* 初始化類
?* Quartz環(huán)境初始化。
?*
?*/
@Configuration
public?class?QuartzConfiguration?{
????/**
?????* 創(chuàng)建Job對象。在Spring環(huán)境中,創(chuàng)建一個類型的對象的時候,很多情況下,都是通過FactoryBean來間接創(chuàng)建的。
?????* 如果有多個Job對象,定義多次方法。
?????*
?????* 在JobDetailFactoryBean類型中,用于創(chuàng)建JobDetail對象的方法,其底層使用的邏輯是:Class.newInstance()
?????* 也就是說,JobDetail對象不是通過Spring容器管理的。
?????* 因為Spring容器不管理JobDetail對象,那么Job中需要自動裝配的屬性,就無法實現(xiàn)自動狀態(tài)。如上JOB的第10行會報空指針異常。
?????*
?????* 解決方案是:將JobDetail加入到Spring容器中,讓Spring容器管理JobDetail對象。
?????* 需要重寫Factory相關(guān)代碼。實現(xiàn)Spring容器管理JobDetail。
?????* @return
?????*/
????@Bean
????public?JobDetailFactoryBean initJobDetailFactoryBean(){
????????JobDetailFactoryBean factoryBean =
????????????????new?JobDetailFactoryBean();
????????// 提供job類型。
????????factoryBean.setJobClass(SpringBootQuartzJobDemo.class);
????????
????????return?factoryBean;
????}
????
????/**
?????* 管理Trigger對象
?????* CronTrigger - 就是Trigger的一個實現(xiàn)類型。其中用于定義周期時間的是CronSchedulerBuilder
?????* 實際上,CronTrigger是用于管理一個Cron表達(dá)式的類型。
?????* @param?jobDetailFactoryBean - 上一個方法初始化的JobDetailFactoryBean
?????* @return
?????*/
????@Bean(name="cronTriggerFactoryBean1")
????public?CronTriggerFactoryBean initCronTriggerFactoryBean(
????????????){
????????CronTriggerFactoryBean factoryBean =
????????????????new?CronTriggerFactoryBean();
????????
????????JobDetailFactoryBean jobDetailFactoryBean = this.initJobDetailFactoryBean();
????????
????????factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
????????
????????factoryBean.setCronExpression("0/3 * * * * ?");
????????
????????return?factoryBean;
????}
????
????/**
?????* 初始化Scheduler
?????* @param?cronTriggerFactoryBean - 上一個方法初始化的CronTriggerFactoryBean
?????* @return
?????*/
????@Bean
????public?SchedulerFactoryBean initSchedulerFactoryBean(
????????????CustomJobFactory customJobFactory,
????????????CronTriggerFactoryBean[] cronTriggerFactoryBean){
????????SchedulerFactoryBean factoryBean =
????????????????new?SchedulerFactoryBean();
????????CronTrigger[] triggers = new?CronTrigger[cronTriggerFactoryBean.length];
????????for(int?i = 0; i < cronTriggerFactoryBean.length; i++){
????????????triggers[i] = cronTriggerFactoryBean[i].getObject();
????????}
????????// 注冊觸發(fā)器,一個Scheduler可以注冊若干觸發(fā)器。
????????factoryBean.setTriggers(triggers);
????????// 為Scheduler設(shè)置JobDetail的工廠。可以覆蓋掉SpringBoot提供的默認(rèn)工廠,保證JobDetail中的自動裝配有效。
????????factoryBean.setJobFactory(customJobFactory);
????????
????????return?factoryBean;
????}
????
}重寫JobFactory:
/**
?* 重寫的工廠對象。
?*/
@Component
public?class?CustomJobFactory extends?AdaptableJobFactory {
????/**
?????* AutowireCapableBeanFactory : 簡單理解為Spring容器,是Spring容器Context的一個Bean對象管理工程。
?????* 可以實現(xiàn)自動裝配邏輯,和對象創(chuàng)建邏輯。
?????* 是SpringIoC容器的一個重要組成部件。
?????*/
????@Autowired
????private?AutowireCapableBeanFactory autowireCapableBeanFactory;
????
????@Override
????protected?Object?createJobInstance(TriggerFiredBundle bundle) throws Exception {
????????// 通過父類型中的方法,創(chuàng)建JobDetail對象。
????????Object?obj = super.createJobInstance(bundle);
????????// 將JobDetail對象加入到Spring容器中,讓Spring容器管理,并實現(xiàn)自動裝配邏輯。
????????this.autowireCapableBeanFactory.autowireBean(obj);
????????
????????return?obj;
????}
}分布式quartz配置
1、資源依賴配置:由于分布式的原因,Quartz中提供分布式處理的jar包以及數(shù)據(jù)庫及連接相關(guān)的依賴。
????????
????????<dependency>
????????????<groupId>org.springframeworkgroupId>
????????????<artifactId>spring-context-supportartifactId>
????????dependency>
????????
????????<dependency>
????????????<groupId>org.quartz-schedulergroupId>
????????????<artifactId>quartzartifactId>
????????????<version>2.2.1version>
????????????<exclusions>
????????????????<exclusion>
????????????????????<artifactId>slf4j-apiartifactId>
????????????????????<groupId>org.slf4jgroupId>
????????????????exclusion>
????????????exclusions>
????????dependency>
????????
????????<dependency>
????????????<groupId>org.quartz-schedulergroupId>
????????????<artifactId>quartz-jobsartifactId>
????????????<version>2.2.1version>
????????dependency>
????????
????????<dependency>
????????????<groupId>org.springframeworkgroupId>
????????????<artifactId>spring-txartifactId>
????????dependency>
????????
????????<dependency>
????????????<groupId>org.springframework.bootgroupId>
????????????<artifactId>spring-boot-starter-webartifactId>
????????dependency>
????????
????????<dependency>
????????????<groupId>org.springframework.bootgroupId>
????????????<artifactId>spring-boot-starter-data-jpaartifactId>
????????dependency>
????????
????????<dependency>
????????????<groupId>mysqlgroupId>
????????????<artifactId>mysql-connector-javaartifactId>
????????dependency>
????????
????????<dependency>
????????????<groupId>com.alibabagroupId>
????????????<artifactId>druidartifactId>
????????????<version>1.0.9version>
????????dependency>
dependencies>2、提供數(shù)據(jù)庫相關(guān)配置:
spring.datasource.url=jdbc:mysql://localhost:3306/quartz?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=600000
spring.datasource.timeBetweenEvictionRunsMillis=600000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
server.port=80803、提供Quartz配置信息,為Quartz提供數(shù)據(jù)庫配置信息,如數(shù)據(jù)庫,表格的前綴之類的。
#?是否使用properties作為數(shù)據(jù)存儲
org.quartz.jobStore.useProperties=false
#?數(shù)據(jù)庫中的表格命名前綴
org.quartz.jobStore.tablePrefix = QRTZ_
#?是否是一個集群,是不是分布式的任務(wù)
org.quartz.jobStore.isClustered = true
#?集群檢查周期,單位毫秒。可以自定義縮短時間。當(dāng)某一個節(jié)點宕機(jī)的時候,其他節(jié)點等待多久后開始執(zhí)行任務(wù)。
org.quartz.jobStore.clusterCheckinInterval = 5000
#?單位毫秒, 集群中的節(jié)點退出后,再次檢查進(jìn)入的時間間隔。
org.quartz.jobStore.misfireThreshold = 60000
#?事務(wù)隔離級別
org.quartz.jobStore.txIsolationLevelReadCommitted = true
#?存儲的事務(wù)管理類型
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#?使用的Delegate類型
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#?集群的命名,一個集群要有相同的命名。
org.quartz.scheduler.instanceName = ClusterQuartz
#?節(jié)點的命名,可以自定義。AUTO代表自動生成。
org.quartz.scheduler.instanceId= AUTO
#?rmi遠(yuǎn)程協(xié)議是否發(fā)布
org.quartz.scheduler.rmi.export = false
#?rmi遠(yuǎn)程協(xié)議代理是否創(chuàng)建
org.quartz.scheduler.rmi.proxy = false
#?是否使用用戶控制的事務(wù)環(huán)境觸發(fā)執(zhí)行job。
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false4、初始化數(shù)據(jù)庫
建表語句可以自己在官方網(wǎng)站中查找(Quartz-lib中),使用tables-mysql.sql建表。
5、定義JOB類
啟動器和普通quartz無差異,但是JOB自身定義有些許差異:
/**
?* 使用Spring提供的Quartz相關(guān)Job類型實現(xiàn)Job的定義。
?* 父類型QuartzJobBean中,提供了分布式環(huán)境中任務(wù)的配置定義。
?* 保證分布式環(huán)境中的任務(wù)是有效的。
?*/
@PersistJobDataAfterExecution?// 當(dāng)job執(zhí)行結(jié)束,持久化job信息到數(shù)據(jù)庫
@DisallowConcurrentExecution?// 保證job的唯一性(單例)
public class SpringBootQuartzJobDemo extends QuartzJobBean {
????@Override
????protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
????????System.out.println("SpringBootQuartzJobDemo : "?+ new Date());
????}
}6、QuartzConfiguration類型定義
@Configuration
public?class?QuartzConfiguration?{
????@Autowired
????private?DataSource dataSource;
????/**
?????* 創(chuàng)建調(diào)度器, 可以省略的。
?????* @return
?????* @throws?Exception
?????*/
????@Bean
????public?Scheduler scheduler()?throws?Exception {
????????Scheduler scheduler = schedulerFactoryBean().getScheduler();
????????scheduler.start();
????????return?scheduler;
????}
????/**
?????* 創(chuàng)建調(diào)度器工廠bean對象。
?????* @return
?????* @throws?IOException
?????*/
????@Bean
????public?SchedulerFactoryBean schedulerFactoryBean()?throws?IOException {
????????SchedulerFactoryBean factory = new?SchedulerFactoryBean();
????????factory.setSchedulerName("Cluster_Scheduler");
????????factory.setDataSource(dataSource);
????????factory.setApplicationContextSchedulerContextKey("applicationContext");
????????// 設(shè)置調(diào)度器中的線程池。
????????factory.setTaskExecutor(schedulerThreadPool());
????????// 設(shè)置觸發(fā)器
????????factory.setTriggers(trigger().getObject());
????????// 設(shè)置quartz的配置信息
????????factory.setQuartzProperties(quartzProperties());
????????return?factory;
????}
????/**
?????* 讀取quartz.properties配置文件的方法。
?????* @return
?????* @throws?IOException
?????*/
????@Bean
????public?Properties quartzProperties()?throws?IOException {
????????PropertiesFactoryBean propertiesFactoryBean = new?PropertiesFactoryBean();
????????propertiesFactoryBean.setLocation(new?ClassPathResource("/quartz.properties"));
????????// 在quartz.properties中的屬性被讀取并注入后再初始化對象
????????propertiesFactoryBean.afterPropertiesSet();
????????return?propertiesFactoryBean.getObject();
????}
????/**
?????* 創(chuàng)建Job對象的方法。
?????* @return
?????*/
????@Bean
????public?JobDetailFactoryBean job()?{
????????JobDetailFactoryBean jobDetailFactoryBean = new?JobDetailFactoryBean();
????????jobDetailFactoryBean.setJobClass(SpringBootQuartzJobDemo.class);
????????// 是否持久化job內(nèi)容
????????jobDetailFactoryBean.setDurability(true);
????????// 設(shè)置是否多次請求嘗試任務(wù)。
????????jobDetailFactoryBean.setRequestsRecovery(true);
????????return?jobDetailFactoryBean;
????}
????/**
?????* 創(chuàng)建trigger factory bean對象。
?????* @return
?????*/
????@Bean
????public?CronTriggerFactoryBean trigger()?{
????????CronTriggerFactoryBean cronTriggerFactoryBean = new?CronTriggerFactoryBean();
????????cronTriggerFactoryBean.setJobDetail(job().getObject());
????????cronTriggerFactoryBean.setCronExpression("0/2 * * * * ?");
????????return?cronTriggerFactoryBean;
????}
????/**
?????* 創(chuàng)建一個調(diào)度器的線程池。
?????* @return
?????*/
????@Bean
????public?Executor schedulerThreadPool()?{
????????ThreadPoolTaskExecutor executor = new?ThreadPoolTaskExecutor();
????????executor.setCorePoolSize(15);
????????executor.setMaxPoolSize(25);
????????executor.setQueueCapacity(100);
????????return?executor;
????}
}若JOB任務(wù)有定義調(diào)用業(yè)務(wù)等內(nèi)容,也需要重寫JobFactory,如上述常規(guī)quartz,此處不再贅述。
題外話:推薦一個GitHub項目,這個 GitHub 整理了上百本常用技術(shù)PDF,絕大部分核心的技術(shù)書籍都可以在這里找到,GitHub地址:https://github.com/gsjqwyl/awesome-ebook(電腦打開體驗更好),地址閱讀原文直達(dá)。麻煩打個給個Star,持續(xù)更新中...
---END---
文末福利


