Transactional注解導(dǎo)致Spring Bean定時(shí)任務(wù)失效的解決方法
背景
業(yè)務(wù)需要定時(shí)撈取數(shù)據(jù)庫(kù)中新增的數(shù)據(jù)做數(shù)據(jù)處理及分析,更新?tīng)顟B(tài),處理結(jié)束。而我們不能隨意定義線程池,規(guī)定使用統(tǒng)一的標(biāo)準(zhǔn)規(guī)范來(lái)定義線程池。如在配置文件中配置線程池的屬性:名稱,線程核心數(shù)等,任務(wù)屬性:任務(wù)名稱,任務(wù)處理類,延遲信息等等。定義好這些信息后,啟動(dòng)系統(tǒng)時(shí),線程池就會(huì)初始化并開(kāi)始執(zhí)行任務(wù)。
業(yè)務(wù)實(shí)現(xiàn)
Spring監(jiān)聽(tīng)器
使用Spring容器啟動(dòng)結(jié)束發(fā)布的ApplicationReadyEvent事件來(lái)初始化線程池。
@EventListener
public void onApplicationReadyEvent(ApplicationReadyEvent event) {
log.info("監(jiān)聽(tīng)器啟動(dòng)線程池。。。");
jobManager.start();
}
線程池統(tǒng)一處理類
public void start() {
AbstractJob job = context.getAutowireCapableBeanFactory().createBean(BusinessJob.class);
threadPool.scheduleWithFixedDelay(job, 1000, 1000, TimeUnit.MILLISECONDS);
}
任務(wù)處理抽象類
所有任務(wù)都會(huì)繼承這個(gè)抽象類,它定義了一些公共的行為,比如看門(mén)狗監(jiān)視任務(wù)是否正常執(zhí)行??撮T(mén)口屬性被定義為這個(gè)抽象類的屬性,它是直接導(dǎo)致任務(wù)失效的直接原因
@Override
public void run() {
log.info("==========AbstractJob start===========");
try {
work();
watchDog.print();
} catch (Throwable t) {
logger.log(Level.WARNING, "aaa bbb ccc", t);
}
log.info("==========AbstractJob end=============");
}
protected abstract void work();
任務(wù)處理類(繼承上面的抽象類)
該類被定義為Spring Bean對(duì)象
@Override
public void work() {
log.info("job start.");
handle();
log.info("job end.");
}
public void handle(){
// 處理業(yè)務(wù)
}
新需求
由于某種原因業(yè)務(wù)提出新需求,而這個(gè)需求需要支持事務(wù),于是根據(jù)以前學(xué)過(guò)的知識(shí),直接在任務(wù)處理類中定義@Transactional注解的方法,通過(guò)Spring循環(huán)依賴,注入了自己。
@Override
public void work() {
log.info("job start.");
handle();
// job
job.testTransaction();
log.info("job end.");
}
@Transactional
public void testTransaction() {
log.info("execute transaction.");
jdbcTemplate.execute("update user set name='rick1' where id = 3");
// jdbcTemplate.execute("insert into user values('1', 'rick')");
log.info("execute transaction end.");
}
本地測(cè)試發(fā)現(xiàn)執(zhí)行正常,提交代碼。
萬(wàn)萬(wàn)沒(méi)想到,測(cè)試反饋,定時(shí)任務(wù)只跑了一次就停止了,也沒(méi)有異常信息
也是本地重新啟動(dòng)發(fā)現(xiàn)確實(shí)跑了一次任務(wù)就停了。于是將@Transactional注解干掉,任務(wù)正常的執(zhí)行。所以將事務(wù)方法重新定義一個(gè)類,加上@Component注解,通過(guò)bean對(duì)象引入到任務(wù)類中。
至此,業(yè)務(wù)是開(kāi)發(fā)完了,但是出現(xiàn)這種問(wèn)題的原因還沒(méi)有分析清楚,隨后就有了上面的demo復(fù)現(xiàn)問(wèn)題。
猜測(cè)
@Transactional注解原理是生成一個(gè)代理對(duì)象包裹原生創(chuàng)建的Bean對(duì)象,是不是啟動(dòng)時(shí)生成的代理對(duì)象將原來(lái)傳遞到線程池的任務(wù)被丟棄了。于是把所有涉及的源碼開(kāi)始分析起來(lái)
獲取任務(wù)添加到線程池
從Spring容器中獲取的Bean對(duì)象是個(gè)代理對(duì)象,所以線程池里面執(zhí)行的任務(wù)是個(gè)代理對(duì)象

ScheduledThreadPoolExecutor線程池
執(zhí)行scheduleWithFixedDelay()方法
檢驗(yàn)
封裝任務(wù)
調(diào)用delayedExecute()方法執(zhí)行任務(wù),最終調(diào)用ThreadPoolExecutor類ensurePrestart()方法,將任務(wù)提交到線程池執(zhí)行

線程池啟動(dòng)線程執(zhí)行的是ScheduledThreadPoolExecutor內(nèi)部類的ScheduledFutureTask類run()方法

第一次執(zhí)行任務(wù)時(shí)調(diào)用的是runAndReset()方法,如果任務(wù)執(zhí)行成功,則返回true,通過(guò)reExecutePeriodic()將任務(wù)重新添加到線程池去執(zhí)行;如果任務(wù)執(zhí)行失敗拋異常,則返回false,任務(wù)就被丟棄了,也就是跑一次,后面就不跑了。

順著這個(gè)思路返回去看任務(wù)執(zhí)行過(guò)程,如果拋異常了,那就證明這個(gè)迷就解開(kāi)了。c.call()就會(huì)調(diào)用我們定義的任務(wù)抽象類,它又會(huì)調(diào)用work()方法,而從日志得知work()正常執(zhí)行完成,所以問(wèn)題極大可能出現(xiàn)在抽象類里面,work()執(zhí)行完了以后調(diào)用watchDog對(duì)象的方法,此時(shí)Debug發(fā)現(xiàn)watchDog對(duì)象為空,也就出現(xiàn)了空指針異常,這個(gè)異常會(huì)被捕獲,并打印出來(lái),此時(shí)又離譜的事來(lái)了,任務(wù)類的代理對(duì)象的logger屬性又是空的,所以又出現(xiàn)了空指針異常拋出去了,導(dǎo)致任務(wù)停止執(zhí)行。
為什么代理對(duì)象的屬性都為空呢
Spring代理對(duì)象所有屬性都為空,只有被代理對(duì)象的屬性有值。
以上就是Transactional注解導(dǎo)致Spring Bean定時(shí)任務(wù)失效的解決方法的詳細(xì)內(nèi)容,更多關(guān)于Transactional導(dǎo)致Spring Bean定時(shí)失效的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java集合List和Map互轉(zhuǎn)的方法總結(jié)
有時(shí)候我們需要將給定的List轉(zhuǎn)換為Map,或者M(jìn)ap轉(zhuǎn)換為L(zhǎng)ist,本文主要介紹了Java集合List和Map互轉(zhuǎn)的方法總結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
Maven3種打包方式中maven-assembly-plugin的使用詳解
這篇文章主要介紹了Maven3種打包方式中maven-assembly-plugin的使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
Java如何接收XML格式參數(shù)并轉(zhuǎn)換為JSON
在 Java 應(yīng)用程序中,處理 XML 數(shù)據(jù)并將其轉(zhuǎn)換為 JSON 格式是很常見(jiàn)的任務(wù),這篇文章為大家整理了一下具體的實(shí)現(xiàn)方法,希望對(duì)大家有所幫助2025-03-03
解析SpringBoot @EnableAutoConfiguration的使用
這篇文章主要介紹了解析SpringBoot @EnableAutoConfiguration的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
簡(jiǎn)述IDEA集成Git在實(shí)際項(xiàng)目中的運(yùn)用
這篇文章主要介紹了IDEA集成Git在實(shí)際項(xiàng)目中的運(yùn)用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
JDK1.6“新“特性Instrumentation之JavaAgent(推薦)
這篇文章主要介紹了JDK1.6“新“特性Instrumentation之JavaAgent,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08
SpringBoot3和mybatis-plus整合出現(xiàn)的問(wèn)題解決辦法
SpringBoot和MybatisPlus的整合可以讓我們更加方便地進(jìn)行數(shù)據(jù)庫(kù)操作,這篇文章主要給大家介紹了關(guān)于SpringBoot3和mybatisplus整合出現(xiàn)的一些問(wèn)題的相關(guān)資料,需要的朋友可以參考下2024-01-01
SpringBoot3.3.X整合Mybatis-Plus的實(shí)現(xiàn)示例
本文介紹了在Spring Boot 3.3.2中整合MyBatis-Plus 3.5.7,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03
SpringBoot與Spring中數(shù)據(jù)緩存Cache超詳細(xì)講解
我們知道內(nèi)存讀取速度遠(yuǎn)大于硬盤(pán)讀取速度,當(dāng)需要重復(fù)獲取相同數(shù)據(jù)時(shí),一次一次的請(qǐng)求數(shù)據(jù)庫(kù)或者遠(yuǎn)程服務(wù),導(dǎo)致在數(shù)據(jù)庫(kù)查詢或者遠(yuǎn)程方法調(diào)用上小號(hào)大量的時(shí)間,最終導(dǎo)致程序性能降低,這就是數(shù)據(jù)緩存要解決的問(wèn)題,學(xué)過(guò)計(jì)算機(jī)組成原理或者操作系統(tǒng)的同學(xué)們應(yīng)該比較熟悉2022-10-10

