Transactional注解导致Spring Bean定时任务失效的解决方法

 更新时间:2024年10月12日 11:56:24   作者:Butterfly(Papillon)  
这篇文章主要介绍了Transactional注解导致Spring Bean定时任务失效的解决方法,文中通过代码示例介绍的非常详细,对大家解决问题有一定的帮助,需要的朋友可以参考下

背景

业务需要定时捞取数据库中新增的数据做数据处理及分析,更新状态,处理结束。而我们不能随意定义线程池,规定使用统一的标准规范来定义线程池。如在配置文件中配置线程池的属性:名称,线程核心数等,任务属性:任务名称,任务处理类,延迟信息等等。定义好这些信息后,启动系统时,线程池就会初始化并开始执行任务。

业务实现

Spring监听器
使用Spring容器启动结束发布的ApplicationReadyEvent事件来初始化线程池。

	@EventListener
    public void onApplicationReadyEvent(ApplicationReadyEvent event) {
        log.info("监听器启动线程池。。。");
        jobManager.start();
    }

线程池统一处理类

public void start() {
       AbstractJob job = context.getAutowireCapableBeanFactory().createBean(BusinessJob.class);
       threadPool.scheduleWithFixedDelay(job, 1000, 1000, TimeUnit.MILLISECONDS);
   }

任务处理抽象类
所有任务都会继承这个抽象类,它定义了一些公共的行为,比如看门狗监视任务是否正常执行。看门口属性被定义为这个抽象类的属性,它是直接导致任务失效的直接原因

    @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(){
        // 处理业务
    }

新需求

由于某种原因业务提出新需求,而这个需求需要支持事务,于是根据以前学过的知识,直接在任务处理类中定义@Transactional注解的方法,通过Spring循环依赖,注入了自己。

	@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.");
    }

本地测试发现执行正常,提交代码。
万万没想到,测试反馈,定时任务只跑了一次就停止了,也没有异常信息
也是本地重新启动发现确实跑了一次任务就停了。于是将@Transactional注解干掉,任务正常的执行。所以将事务方法重新定义一个类,加上@Component注解,通过bean对象引入到任务类中。
至此,业务是开发完了,但是出现这种问题的原因还没有分析清楚,随后就有了上面的demo复现问题。

猜测

@Transactional注解原理是生成一个代理对象包裹原生创建的Bean对象,是不是启动时生成的代理对象将原来传递到线程池的任务被丢弃了。于是把所有涉及的源码开始分析起来

获取任务添加到线程池
从Spring容器中获取的Bean对象是个代理对象,所以线程池里面执行的任务是个代理对象

在这里插入图片描述

ScheduledThreadPoolExecutor线程池
执行scheduleWithFixedDelay()方法
检验
封装任务
调用delayedExecute()方法执行任务,最终调用ThreadPoolExecutor类ensurePrestart()方法,将任务提交到线程池执行

在这里插入图片描述

线程池启动线程执行的是ScheduledThreadPoolExecutor内部类的ScheduledFutureTask类run()方法

在这里插入图片描述

第一次执行任务时调用的是runAndReset()方法,如果任务执行成功,则返回true,通过reExecutePeriodic()将任务重新添加到线程池去执行;如果任务执行失败抛异常,则返回false,任务就被丢弃了,也就是跑一次,后面就不跑了。

在这里插入图片描述

顺着这个思路返回去看任务执行过程,如果抛异常了,那就证明这个迷就解开了。c.call()就会调用我们定义的任务抽象类,它又会调用work()方法,而从日志得知work()正常执行完成,所以问题极大可能出现在抽象类里面,work()执行完了以后调用watchDog对象的方法,此时Debug发现watchDog对象为空,也就出现了空指针异常,这个异常会被捕获,并打印出来,此时又离谱的事来了,任务类的代理对象的logger属性又是空的,所以又出现了空指针异常抛出去了,导致任务停止执行。

为什么代理对象的属性都为空呢

Spring代理对象所有属性都为空,只有被代理对象的属性有值。

以上就是Transactional注解导致Spring Bean定时任务失效的解决方法的详细内容,更多关于Transactional导致Spring Bean定时失效的资料请关注脚本之家其它相关文章!

相关文章

  • 详解java NIO之Channel(通道)

    详解java NIO之Channel(通道)

    这篇文章主要介绍了详解java NIO之Channel(通道)的相关资料,文中讲解非常详细,示例代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • SpringBoot解决数据库时间和返回时间格式不一致的问题

    SpringBoot解决数据库时间和返回时间格式不一致的问题

    这篇文章主要介绍了SpringBoot解决数据库时间和返回时间格式不一致的问题,文章通过代码示例和图文结合的方式讲解的非常详细,对大家的学习和工作有一定的帮助,需要的朋友可以参考下
    2024-03-03
  • macOS中搭建Java8开发环境(基于Intel x86 64-bit)

    macOS中搭建Java8开发环境(基于Intel x86 64-bit)

    这篇文章主要介绍了macOS中搭建Java8开发环境(基于Intel x86 64-bit) 的相关资料,需要的朋友可以参考下
    2022-12-12
  • 解决SpringBoot使用yaml作为配置文件遇到的坑

    解决SpringBoot使用yaml作为配置文件遇到的坑

    这篇文章主要介绍了解决SpringBoot使用yaml作为配置文件遇到的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 基于springboot+jwt实现刷新token过程解析

    基于springboot+jwt实现刷新token过程解析

    这篇文章主要介绍了基于springboot+jwt实现刷新token过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • 线程阻塞唤醒工具 LockSupport使用详解

    线程阻塞唤醒工具 LockSupport使用详解

    这篇文章主要为大家介绍了线程阻塞唤醒工具LockSupport使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • 深入浅出探究Java多态的实现和应用

    深入浅出探究Java多态的实现和应用

    多态是实现面向对象的软件技术中必不可少的一个内容,所以这篇文章主要来和大家讲解一下Java多态的实现和应用,感兴趣的小伙伴可以了解一下
    2023-07-07
  • Java代码是如何被CPU狂飙起来的

    Java代码是如何被CPU狂飙起来的

    无论是刚刚入门Java的新手还是已经工作了的老司机,恐怕都不容易把Java代码如何一步步被CPU执行起来这个问题完全讲清楚。本文就带你详细了解Java代码到底是怎么运行起来的。感兴趣的同学可以参考阅读
    2023-03-03
  • mybatis配置mapper-locations的坑及解决

    mybatis配置mapper-locations的坑及解决

    这篇文章主要介绍了mybatis配置mapper-locations的坑及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Spring boot+mybatis+thymeleaf 实现登录注册增删改查功能的示例代码

    Spring boot+mybatis+thymeleaf 实现登录注册增删改查功能的示例代码

    这篇文章主要介绍了Spring boot+mybatis+thymeleaf 实现登录注册增删改查功能的示例代码,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07

最新评论