Transactional注解導致Spring Bean定時任務失效的解決方法
背景
業(yè)務需要定時撈取數據庫中新增的數據做數據處理及分析,更新狀態(tài),處理結束。而我們不能隨意定義線程池,規(guī)定使用統一的標準規(guī)范來定義線程池。如在配置文件中配置線程池的屬性:名稱,線程核心數等,任務屬性:任務名稱,任務處理類,延遲信息等等。定義好這些信息后,啟動系統時,線程池就會初始化并開始執(zhí)行任務。
業(yè)務實現
Spring監(jiān)聽器
使用Spring容器啟動結束發(fā)布的ApplicationReadyEvent事件來初始化線程池。
@EventListener public void onApplicationReadyEvent(ApplicationReadyEvent event) { log.info("監(jiān)聽器啟動線程池。。。"); jobManager.start(); }
線程池統一處理類
public void start() { AbstractJob job = context.getAutowireCapableBeanFactory().createBean(BusinessJob.class); threadPool.scheduleWithFixedDelay(job, 1000, 1000, TimeUnit.MILLISECONDS); }
任務處理抽象類
所有任務都會繼承這個抽象類,它定義了一些公共的行為,比如看門狗監(jiān)視任務是否正常執(zhí)行??撮T口屬性被定義為這個抽象類的屬性,它是直接導致任務失效的直接原因
@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();
任務處理類(繼承上面的抽象類)
該類被定義為Spring Bean對象
@Override public void work() { log.info("job start."); handle(); log.info("job end."); } public void handle(){ // 處理業(yè)務 }
新需求
由于某種原因業(yè)務提出新需求,而這個需求需要支持事務,于是根據以前學過的知識,直接在任務處理類中定義@Transactional注解的方法,通過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."); }
本地測試發(fā)現執(zhí)行正常,提交代碼。
萬萬沒想到,測試反饋,定時任務只跑了一次就停止了,也沒有異常信息
也是本地重新啟動發(fā)現確實跑了一次任務就停了。于是將@Transactional注解干掉,任務正常的執(zhí)行。所以將事務方法重新定義一個類,加上@Component注解,通過bean對象引入到任務類中。
至此,業(yè)務是開發(fā)完了,但是出現這種問題的原因還沒有分析清楚,隨后就有了上面的demo復現問題。
猜測
@Transactional注解原理是生成一個代理對象包裹原生創(chuàng)建的Bean對象,是不是啟動時生成的代理對象將原來傳遞到線程池的任務被丟棄了。于是把所有涉及的源碼開始分析起來
獲取任務添加到線程池
從Spring容器中獲取的Bean對象是個代理對象,所以線程池里面執(zhí)行的任務是個代理對象
ScheduledThreadPoolExecutor線程池
執(zhí)行scheduleWithFixedDelay()方法
檢驗
封裝任務
調用delayedExecute()方法執(zhí)行任務,最終調用ThreadPoolExecutor類ensurePrestart()方法,將任務提交到線程池執(zhí)行
線程池啟動線程執(zhí)行的是ScheduledThreadPoolExecutor內部類的ScheduledFutureTask類run()方法
第一次執(zhí)行任務時調用的是runAndReset()方法,如果任務執(zhí)行成功,則返回true,通過reExecutePeriodic()將任務重新添加到線程池去執(zhí)行;如果任務執(zhí)行失敗拋異常,則返回false,任務就被丟棄了,也就是跑一次,后面就不跑了。
順著這個思路返回去看任務執(zhí)行過程,如果拋異常了,那就證明這個迷就解開了。c.call()就會調用我們定義的任務抽象類,它又會調用work()方法,而從日志得知work()正常執(zhí)行完成,所以問題極大可能出現在抽象類里面,work()執(zhí)行完了以后調用watchDog對象的方法,此時Debug發(fā)現watchDog對象為空,也就出現了空指針異常,這個異常會被捕獲,并打印出來,此時又離譜的事來了,任務類的代理對象的logger屬性又是空的,所以又出現了空指針異常拋出去了,導致任務停止執(zhí)行。
為什么代理對象的屬性都為空呢
Spring代理對象所有屬性都為空,只有被代理對象的屬性有值。
以上就是Transactional注解導致Spring Bean定時任務失效的解決方法的詳細內容,更多關于Transactional導致Spring Bean定時失效的資料請關注腳本之家其它相關文章!
相關文章
Maven3種打包方式中maven-assembly-plugin的使用詳解
這篇文章主要介紹了Maven3種打包方式中maven-assembly-plugin的使用,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07解析SpringBoot @EnableAutoConfiguration的使用
這篇文章主要介紹了解析SpringBoot @EnableAutoConfiguration的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09JDK1.6“新“特性Instrumentation之JavaAgent(推薦)
這篇文章主要介紹了JDK1.6“新“特性Instrumentation之JavaAgent,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08SpringBoot3和mybatis-plus整合出現的問題解決辦法
SpringBoot和MybatisPlus的整合可以讓我們更加方便地進行數據庫操作,這篇文章主要給大家介紹了關于SpringBoot3和mybatisplus整合出現的一些問題的相關資料,需要的朋友可以參考下2024-01-01SpringBoot3.3.X整合Mybatis-Plus的實現示例
本文介紹了在Spring Boot 3.3.2中整合MyBatis-Plus 3.5.7,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-03-03SpringBoot與Spring中數據緩存Cache超詳細講解
我們知道內存讀取速度遠大于硬盤讀取速度,當需要重復獲取相同數據時,一次一次的請求數據庫或者遠程服務,導致在數據庫查詢或者遠程方法調用上小號大量的時間,最終導致程序性能降低,這就是數據緩存要解決的問題,學過計算機組成原理或者操作系統的同學們應該比較熟悉2022-10-10