Springboot配置@Async无效的解决方案

 更新时间:2023年09月27日 09:57:50   作者:唐宋xy  
这篇文章主要介绍了Springboot配置@Async无效的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

Springboot配置@Async无效

Springboot中为了同时执行多个任务,或者同时查询,加快查询则使用多线程查询,在springboot中,可以直接使用 @Async注解来实现异步任务,可以参考Springboot使用@Async实现异步任务简单快捷使用,但是异步任务在某些情况下居然无效。

配置正确,代码不报错,但是无法使用多线程异步任务,可能是下面的原因导致:

1.没有在springboot启动类当中添加注解 @EnableAsync注解。

2.异步方法使用注解@Async的返回值只能为void或者Future。

3.没有走Spring的代理类。

因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理实现的。

那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,没有经过Spring容器,无法使用代理对象调用。

4.注解的方法必须是 public 方法。编译时非public方法会报错

5.如果需要从 类的内部 调用,需要先获取其代理类

先注入ApplicationContext

@Autowired
private ApplicationContext applicationContext;

然后在方法中通过ApplicationContext对象获取当前类对象

applicationContext.getBean(Class);

6.调用的是静态(static )方法,也会导致注解失效,因为注解是基于代理对象调用,而static方法是属于类的,无法通过spring的代理对象直接调用

Springboot @Async失效的坑

异步应用场景

为了提高接口的响应性能,当业务非常复杂的情况下,可以将一部分跟业务关联性不是特别强的逻辑进行异步处理。如日志记录、短信发送、增加积分等。

通常而言会将此类业务逻辑通过异步的方式进行处理,从而加快接口的响应速度,常用的解决方案有:

  • 使用JDK 自定义线程池 让代码异步执行
  • 在springboot 中 使用@Async注解进行异步处理
  • 使用中间件如mq 消息通知让下游异步消费 如RocketMQ、KAFKA

使用第一种方式,需要精通线程池运行原理,结合实际的业务场景对队列大小进行合理的设置。队列设置过大过小都会存在内存溢出的风险。

第三种方式是最合理的方式,它能够通过MQ进行削峰填谷,通过合理的参数配置,保证数据不会丢失。但是架构改动过大,对小型的单体应用来讲,工作量过大,成本过高。

在springboot 大行其道的情况下,考虑开发成本,以及项目时间关系选用第二种方式来解决代码异步执行的问题。

真实业务场景

线上问题

一个工单的分页列表,前端控制了每个列表最大的显示条数为100条。在业务流程中存在工单转移的操作,转移一笔工单至少包含以下几个重要的步骤:

  • 新增工单处理日志,如什么时间点将工单转移给某人
  • 修改工单当前处理人
  • 发送企业微信给B端的跟进人(转移人)
  • 发送im信息给C端的用户

由于公司采用微服务架构,因此每个业务模块拆分的很细,在上述步骤中需要从其他系统中通过rpc调用接口拿到需要的数据才能完成整个业务流程数据的拼装,如需要从crm系统拿到组织架构信息,获取转移人的组织架构、需要从udb用户中心获取转移人的企业微信昵称等。

因此在批量转移的时候,前端会出现调用超时的问题,原因是dubbo接口默认的超时时间是15秒,由于业务复杂,导致在15秒内执行不完业务逻辑。

解决方案

  • 将sql处理改为批量执行,如新增处理日志 (batch insert);修改当前工单,使用case when 的方式一次性修改完成(批量update)
  • 将发送消息改成异步处理 加快前端接口的响应速度
  • 让接口提供方提供批量查询的接口,避免rpc 循环调用在网络上的消耗

优化完成之后,接口的响应速度由15秒多,变成了1秒。但是过程中遇到坑了。特此记录一下

技术实现

优化过程

@Async 注解定义为可以放置在方法上和类上,当使用在类上表明类所有的方法都能异步执行。

在Springboot中是需要在方法上加上该注解就可以完美的实现异步执行。

原始方法伪代码如下

/**
 * 原始代码 采用流水式的代码一步步实现 业务逻辑
 */
public void doBusiness(Object args){
  //1.  新增工单处理日志,如什么时间点将工单转移给某人
  //2. 修改工单当前处理人
  //3. 发送企业微信给B端的跟进人(转移人)
  //4. 发送im信息给C端的用户
}

那么异步问题就很好处理了,只需要将方法抽离形成多个子方法, 每个方法执行自己的业务处理逻辑,然后再方法加上@Async注解不就ok了么,伪代码如下

public void doBusiness(Object args){
 	//2. 修改工单当前处理人
  this.doAsyncBusiness();
}
// 单独抽离一个异步执行的方法 加上@Async注解
@Async
private void doAsyncBusiness(Object args){
  //1.  新增工单处理日志,如什么时间点将工单转移给某人
  //3. 发送企业微信给B端的跟进人(转移人)
  //4. 发送im信息给C端的用户
}

打完收工,重启应用,进行测试,然而并没有像预期中的那样,接口的响应速度还是15秒左右。接着排查原因,可以肯定的是@Async是可以提供异步方法执行。应该是我们使用方式不对导致。

@Async 限制

熟悉Springboot AOP的同学可能会发现更改后的代码存在明显的问题

  • 首先AOP代理机制要求 被代理的方法必须是 public , private 方法不能被代理
  • 其次AOP代理机制会生成一个代理类 执行代理方法 注意 this.doAsyncBusiness() 调用的是本对象的方法 ;
  • 在启动类上加上@EnableAsync注解

综上所述,原因我们已经通过AOP代理的原理找到了。下面摘自官方文档的一段话:

  • it must be applied to public methods only
  • self-invocation – calling the async method from within the same class – won’t work

The reasons are simple – 「the method needs to be public」 so that it can be proxied. And 「self-invocation doesn’t work」 because it bypasses the proxy and calls the underlying method directly.

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java分布式流式处理组件Producer分区理论

    java分布式流式处理组件Producer分区理论

    这篇文章主要为大家介绍了java分布式流式处理组件Producer分区理论详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Spring Cloud升级最新Finchley版本的所有坑

    Spring Cloud升级最新Finchley版本的所有坑

    这篇文章主要介绍了Spring Cloud升级最新Finchley版本的所有坑,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • 基于SpringBoot的SSMP的整合案例

    基于SpringBoot的SSMP的整合案例

    这篇文章主要介绍了SpringBoot整合SSMP的详细教程,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • MyBatis-Plus非表字段的三种处理方法小结

    MyBatis-Plus非表字段的三种处理方法小结

    这篇文章主要介绍了MyBatis-Plus非表字段的三种处理方法小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java源码重读之ConcurrentHashMap详解

    Java源码重读之ConcurrentHashMap详解

    ConcurrentHashMap(CHM)是日常开发中使用频率非常高的一种数据结构。本文将从源码角度带大家深入了解一下ConcurrentHashMap的使用,需要的可以收藏一下
    2023-05-05
  • java实现二叉树的创建及5种遍历方法(总结)

    java实现二叉树的创建及5种遍历方法(总结)

    下面小编就为大家带来一篇java实现二叉树的创建及5种遍历方法(总结)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • Java轻松入门冒泡 选择 插入 希尔 归并排序算法

    Java轻松入门冒泡 选择 插入 希尔 归并排序算法

    这篇文章主要介绍了Java常用的排序算法及代码实现,在Java开发中,对排序的应用需要熟练的掌握,这样才能够确保Java学习时候能够有扎实的基础能力。那Java有哪些排序算法呢?本文小编就来详细说说Java常见的排序算法,需要的朋友可以参考一下
    2022-02-02
  • 自己动手在Spring-Boot上加强国际化功能的示例

    自己动手在Spring-Boot上加强国际化功能的示例

    这篇文章主要介绍了自己动手在Spring-Boot上加强国际化功能的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • 百度Java面试题 前200页精选(下)

    百度Java面试题 前200页精选(下)

    这篇文章主要为大家分享了Java面试资源下篇,百度“Java面试题”前200页都在这里了,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • java基于mongodb实现分布式锁的示例代码

    java基于mongodb实现分布式锁的示例代码

    本文主要介绍了java基于mongodb实现分布式锁,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08

最新评论