欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 社会 > Transactional注解导致Spring Bean定时任务失效

Transactional注解导致Spring Bean定时任务失效

2024/10/24 1:55:17 来源:https://blog.csdn.net/weixin_36279234/article/details/142853591  浏览:    关键词:Transactional注解导致Spring Bean定时任务失效

背景

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

业务实现

  • Spring监听器
    使用Spring容器启动结束发布的ApplicationReadyEvent事件来初始化线程池。
	@EventListenerpublic 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);}
  • 任务处理抽象类
    所有任务都会继承这个抽象类,它定义了一些公共的行为,比如看门狗监视任务是否正常执行。看门口属性被定义为这个抽象类的属性,它是直接导致任务失效的直接原因
    @Overridepublic 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对象
	@Overridepublic void work() {log.info("job start.");handle();log.info("job end.");}public void handle(){// 处理业务}

新需求

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

	@Overridepublic void work() {log.info("job start.");handle();// jobjob.testTransaction();log.info("job end.");}@Transactionalpublic 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代理对象所有属性都为空,只有被代理对象的属性有值。可以参考这篇文章

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com