這篇文章給大家介紹springboot+quartz以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù),詳情如下所示:
篇幅較長,耐心的人總能得到最后的答案小生第一次用quartz做定時(shí)任務(wù),不足之處多多諒解。
首先
在springboot項(xiàng)目里做定時(shí)任務(wù)是比較簡單的,最簡單的實(shí)現(xiàn)方式是使用**@Scheduled注解,然后在application啟動(dòng)類上使用@EnableScheduling**開啟定時(shí)任務(wù)。
示例
@SpringBootApplication @EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } // cron為每秒執(zhí)行一次 @Scheduled(cron = "* * * * * ?") public void print(){ System.out.println("執(zhí)行定時(shí)任務(wù)"); } }
######結(jié)果
執(zhí)行定時(shí)任務(wù)
執(zhí)行定時(shí)任務(wù)
執(zhí)行定時(shí)任務(wù)
執(zhí)行定時(shí)任務(wù)
執(zhí)行定時(shí)任務(wù)
執(zhí)行定時(shí)任務(wù)
執(zhí)行定時(shí)任務(wù)
執(zhí)行定時(shí)任務(wù)
簡單的定時(shí)任務(wù)就可以用這種方式來做,cron表達(dá)式的結(jié)果為任務(wù)執(zhí)行的間隔時(shí)間。
然而
實(shí)際開發(fā)中,我們的任務(wù)可能有很多,且需要手動(dòng)操作單個(gè)/全部的任務(wù),比如添加、開啟、停止、繼續(xù)等等操作。那么伴隨著(千牛B類。。。)的BGM有請quartz登場。
quartz
整合
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
quartz的三要素
- 調(diào)度器Scheduler
- 啟動(dòng)觸發(fā)器去執(zhí)行任務(wù)
- 觸發(fā)器Trigger
用來定義Job(任務(wù))觸發(fā)條件、觸發(fā)時(shí)間,觸發(fā)間隔,終止時(shí)間等
任務(wù)job
具體要執(zhí)行的任務(wù)內(nèi)容
使用
使用quartz是需要配置文件的,quartz.properties在quartz的jar包的org.quartz包下可以找到默認(rèn)的配置文件quartz.properties
# Default Properties file for use by StdSchedulerFactory # to create a Quartz Scheduler Instance, if a different # properties file is not explicitly specified. # # 名字 org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false # 實(shí)例化ThreadPool時(shí),使用的線程類為SimpleThreadPool org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool # 線程總個(gè)數(shù) org.quartz.threadPool.threadCount: 10 # 線程的優(yōu)先級 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 # 持久化方式,默認(rèn)持久化在內(nèi)存中,后面我們使用db的方式 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
quartz任務(wù)持久化到db則需要一些官方定義的數(shù)據(jù)庫表,表的sql文件可以在quartz的jar包里找到
坐標(biāo)org.quartz.impl.jdbcjobstore,可以看到里面有很多sql文件,有各種數(shù)據(jù)庫的,咱們用MySQL的,咱們不需要手動(dòng)執(zhí)行sql語句,后面咱們在啟動(dòng)項(xiàng)目的時(shí)候自動(dòng)初始化。
創(chuàng)建我們自己的properties文件
# 實(shí)例化ThreadPool時(shí),使用的線程類為SimpleThreadPool org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool # threadCount和threadPriority將以setter的形式注入ThreadPool實(shí)例 # 并發(fā)個(gè)數(shù) org.quartz.threadPool.threadCount=10 # 優(yōu)先級 org.quartz.threadPool.threadPriority=5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true org.quartz.jobStore.misfireThreshold=5000 #持久化使用的類 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX #數(shù)據(jù)庫中表的前綴 org.quartz.jobStore.tablePrefix=QRTZ_ #數(shù)據(jù)源命名 org.quartz.jobStore.dataSource=qzDS #qzDS 數(shù)據(jù)源,我們使用hikaricp,默認(rèn)的是c3p0 org.quartz.dataSource.qzDS.provider=hikaricp org.quartz.dataSource.qzDS.driver=com.mysql.cj.jdbc.Driver org.quartz.dataSource.qzDS.URL=jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8 org.quartz.dataSource.qzDS.user=root org.quartz.dataSource.qzDS.password=123456 org.quartz.dataSource.qzDS.maxConnections=10
既然我們沒有使用默認(rèn)的連接池,那么就探索一下,上源碼!在這個(gè)包下:org.quartz.utils,有一個(gè)PoolingConnectionProvider,顧名思義,連接池提供者部分源碼
public interface PoolingConnectionProvider extends ConnectionProvider { /** The pooling provider. */ String POOLING_PROVIDER = "provider"; /** The c3p0 pooling provider. */ String POOLING_PROVIDER_C3P0 = "c3p0"; /** The Hikari pooling provider. */ String POOLING_PROVIDER_HIKARICP = "hikaricp"; }
然后HikariCpPoolingConnectionProvider這個(gè)類實(shí)現(xiàn)了PoolingConnectionProvider,自行查看。我們可以在org.quartz.impl下的StdSchedulerFactory中搜索c3p0找到
if(poolingProvider != null && poolingProvider.equals(PoolingConnectionProvider.POOLING_PROVIDER_HIKARICP)) { cpClass = "org.quartz.utils.HikariCpPoolingConnectionProvider"; } else { cpClass = "org.quartz.utils.C3p0PoolingConnectionProvider"; }
剩下的自己多看看吧,起始源碼研究起來沒有想象中那么難那么乏味(我也不喜歡看源碼),但是這個(gè)源碼看起來確實(shí)小有成就感。
回到正題頻道,配置application.yml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver password: 123456 url: jdbc:mysql://localhost:3306/quartz?characterEncoding=UTF8&useSSL=false&serverTimezone=GMT%2B8 username: root quartz: jdbc: initialize-schema: always job-store-type: jdbc
initialize-schema: always每次啟動(dòng)項(xiàng)目,總是初始化數(shù)據(jù)庫表自動(dòng)創(chuàng)建表的關(guān)鍵地方,流程是先刪除數(shù)據(jù)庫表,再創(chuàng)建,如果表不存在,則拋異常,但是不會影響后面的生成表,下次再啟動(dòng)項(xiàng)目的時(shí)候,由于表已經(jīng)存在了,所以不會再拋異常了job-store-type: jdbc就是任務(wù)持久化類型,我們用jdbc
我們可能要在job里注入spring對象,不做配置,是無法注入的。
/** * @author: taoym * @date: 2020/6/4 11:32 * @desc: 一定要自定義JobFactory重寫SpringBeanJobFactory的createJobInstance方法,否則在job中是獲取不到spring容器中的bean的 */ @Component public class JobFactory extends SpringBeanJobFactory { @Autowired private AutowireCapableBeanFactory beanFactory; /** * 這里覆蓋了super的createJobInstance方法,對其創(chuàng)建出來的類再進(jìn)行autowire */ @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { Object jobInstance = super.createJobInstance(bundle); beanFactory.autowireBean(jobInstance); return jobInstance; } }
創(chuàng)建quartz的配置文件
@Configuration public class QuartzConfig { @Autowired private JobFactory jobFactory; /** * 讀取quartz.properties 文件 * 將值初始化 * * @return */ @Bean public Properties quartzProperties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setJobFactory(jobFactory); schedulerFactoryBean.setQuartzProperties(quartzProperties()); return schedulerFactoryBean; } /** * 初始化監(jiān)聽器 * * @return */ @Bean public QuartzInitializerListener executorListener() { return new QuartzInitializerListener(); } @Bean(name = "scheduler") public Scheduler scheduler() throws IOException { return schedulerFactoryBean().getScheduler(); } }
創(chuàng)建觸發(fā)器組件
public class TriggerComponent { /** * @author: taoym * @date: 2020/6/1 10:35 * @desc: 構(gòu)建cron觸發(fā)器 */ public static Trigger cronTrigger(String cron) { CronTrigger cronTrigger = TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule(cron).withMisfireHandlingInstructionDoNothing()) .build(); return cronTrigger; } public static Trigger cronTrigger(String cron, JobDataMap jobDataMap) { CronTrigger cronTrigger = TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule(cron).withMisfireHandlingInstructionDoNothing()) .usingJobData(jobDataMap) .build(); return cronTrigger; } }
觸發(fā)器就用這個(gè)組件來獲取就行了。
創(chuàng)建任務(wù)
@DisallowConcurrentExecution public class TestJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { } }
jobExecutionContext這里面可以獲取任務(wù)組、任務(wù)名、觸發(fā)器組、觸發(fā)器名、jobdetail等信息。那個(gè)注解是為了讓同一個(gè)實(shí)例(jobdetail)只能單線程執(zhí)行。可以這么理解,job為接口,jobdetail為實(shí)現(xiàn)類,a是其中一個(gè)實(shí)現(xiàn)類,a需要花費(fèi)100s執(zhí)行一定的操作,而你給的定時(shí)器是沒50s就執(zhí)行一次操作,a在執(zhí)行到一半的時(shí)候又需要開啟一個(gè)線程來執(zhí)行。使用了DisallowConcurrentExecution就相當(dāng)于a沒有把操作執(zhí)行完的時(shí)候,a不允許開啟線程再執(zhí)行當(dāng)前操作。不知道我的描述是否易懂!
按需創(chuàng)建自己的任務(wù)表,我是用定時(shí)任務(wù)做爬蟲的(小爬蟲)
CREATE TABLE `quartz_job` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號', `job_name` varchar(50) DEFAULT '' COMMENT '任務(wù)名', `job_group` varchar(50) DEFAULT '' COMMENT '任務(wù)組名稱', `job_desc` varchar(255) DEFAULT '' COMMENT 'job描述', `cron` varchar(50) DEFAULT '' COMMENT 'cron表達(dá)式', `status` tinyint(1) DEFAULT '0' COMMENT '狀態(tài)', `url` varchar(255) DEFAULT '' COMMENT '請求地址', `param` varchar(255) DEFAULT '' COMMENT '參數(shù)', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8;
我們添加任務(wù)的時(shí)候不和quartz打交道,把任務(wù)放到數(shù)據(jù)庫即可。別慌,后面有用到他的地方。這個(gè)表需要有增刪改查操作,我們會在系統(tǒng)中查詢?nèi)蝿?wù)列表選擇單個(gè)或者所有任務(wù)開始執(zhí)行
執(zhí)行任務(wù)
@Resource private QuartzJobMapper quartzJobMapper; @Autowired private Scheduler scheduler; @Override public String start(Integer id) { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put(k,v); QuartzJob quartzJob = quartzJobMapper.selectByPrimaryKey(id); JobKey jobKey = JobKey.jobKey(quartzJob.getJobName(), quartzJob.getJobGroup()); jobDetail = JobBuilder.newJob(TestJob.class).withIdentity(jobKey).storeDurably().build(); Trigger trigger = TriggerComponent.cronTrigger(quartzJob.getCron(), jobDataMap); try { scheduler.scheduleJob(jobDetail, trigger); quartzJobMapper.updateStatus(true, id); return "開始任務(wù)執(zhí)行成功"; } catch (SchedulerException se) { log.info("開始任務(wù)的時(shí)候發(fā)生了錯(cuò)誤"); } return "開始任務(wù)的時(shí)候發(fā)生了錯(cuò)誤,請檢查日志"; }
最后我又按照此教程上的內(nèi)容粘貼了一遍代碼,可以正常運(yùn)行。
推薦教程:《PHP》
以上是springboot+quartz 以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù)的詳細(xì)內(nèi)容。更多信息請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣服圖片

Undresser.AI Undress
人工智能驅(qū)動(dòng)的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover
用于從照片中去除衣服的在線人工智能工具。

Clothoff.io
AI脫衣機(jī)

Video Face Swap
使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的代碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
功能強(qiáng)大的PHP集成開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級代碼編輯軟件(SublimeText3)

Jasypt介紹Jasypt是一個(gè)java庫,它允許開發(fā)員以最少的努力為他/她的項(xiàng)目添加基本的加密功能,并且不需要對加密工作原理有深入的了解用于單向和雙向加密的高安全性、基于標(biāo)準(zhǔn)的加密技術(shù)。加密密碼,文本,數(shù)字,二進(jìn)制文件...適合集成到基于Spring的應(yīng)用程序中,開放API,用于任何JCE提供程序...添加如下依賴:com.github.ulisesbocchiojasypt-spring-boot-starter2.1.1Jasypt好處保護(hù)我們的系統(tǒng)安全,即使代碼泄露,也可以保證數(shù)據(jù)源的

一、Redis實(shí)現(xiàn)分布式鎖原理為什么需要分布式鎖在聊分布式鎖之前,有必要先解釋一下,為什么需要分布式鎖。與分布式鎖相對就的是單機(jī)鎖,我們在寫多線程程序時(shí),避免同時(shí)操作一個(gè)共享變量產(chǎn)生數(shù)據(jù)問題,通常會使用一把鎖來互斥以保證共享變量的正確性,其使用范圍是在同一個(gè)進(jìn)程中。如果換做是多個(gè)進(jìn)程,需要同時(shí)操作一個(gè)共享資源,如何互斥呢?現(xiàn)在的業(yè)務(wù)應(yīng)用通常是微服務(wù)架構(gòu),這也意味著一個(gè)應(yīng)用會部署多個(gè)進(jìn)程,多個(gè)進(jìn)程如果需要修改MySQL中的同一行記錄,為了避免操作亂序?qū)е屡K數(shù)據(jù),此時(shí)就需要引入分布式鎖了。想要實(shí)現(xiàn)分

1、自定義RedisTemplate1.1、RedisAPI默認(rèn)序列化機(jī)制基于API的Redis緩存實(shí)現(xiàn)是使用RedisTemplate模板進(jìn)行數(shù)據(jù)緩存操作的,這里打開RedisTemplate類,查看該類的源碼信息publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations,BeanClassLoaderAware{//聲明了key、value的各種序列化方式,初始值為空@NullableprivateRedisSe

springboot讀取文件,打成jar包后訪問不到最新開發(fā)出現(xiàn)一種情況,springboot打成jar包后讀取不到文件,原因是打包之后,文件的虛擬路徑是無效的,只能通過流去讀取。文件在resources下publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

使用場景1、下單成功,30分鐘未支付。支付超時(shí),自動(dòng)取消訂單2、訂單簽收,簽收后7天未進(jìn)行評價(jià)。訂單超時(shí)未評價(jià),系統(tǒng)默認(rèn)好評3、下單成功,商家5分鐘未接單,訂單取消4、配送超時(shí),推送短信提醒……對于延時(shí)比較長的場景、實(shí)時(shí)性不高的場景,我們可以采用任務(wù)調(diào)度的方式定時(shí)輪詢處理。如:xxl-job今天我們采

在Springboot+Mybatis-plus不使用SQL語句進(jìn)行多表添加操作我所遇到的問題準(zhǔn)備工作在測試環(huán)境下模擬思維分解一下:創(chuàng)建出一個(gè)帶有參數(shù)的BrandDTO對象模擬對后臺傳遞參數(shù)我所遇到的問題我們都知道,在我們使用Mybatis-plus中進(jìn)行多表操作是極其困難的,如果你不使用Mybatis-plus-join這一類的工具,你只能去配置對應(yīng)的Mapper.xml文件,配置又臭又長的ResultMap,然后再去寫對應(yīng)的sql語句,這種方法雖然看上去很麻煩,但具有很高的靈活性,可以讓我們

SpringBoot和SpringMVC都是Java開發(fā)中常用的框架,但它們之間有一些明顯的差異。本文將探究這兩個(gè)框架的特點(diǎn)和用途,并對它們的差異進(jìn)行比較。首先,我們來了解一下SpringBoot。SpringBoot是由Pivotal團(tuán)隊(duì)開發(fā)的,它旨在簡化基于Spring框架的應(yīng)用程序的創(chuàng)建和部署。它提供了一種快速、輕量級的方式來構(gòu)建獨(dú)立的、可執(zhí)行

一、@Import引入普通類@Import引入普通的類可以幫助我們把普通的類定義為Bean。@Import可以添加在@SpringBootApplication(啟動(dòng)類)、@Configuration(配置類)、@Component(組件類)對應(yīng)的類上。注意:@RestController、@Service、@Repository都屬于@Component@SpringBootApplication@Import(ImportBean.class)//通過@Import注解把ImportBean
