开发故事:一个 @Async 如何搞瘫整个微服务系统

简介: 大家好,我是小米,一个热爱分享技术的29岁开发者。本文讲述了一个困扰我们团队的开发环境问题,最终发现罪魁祸首竟是 `@Async` 注解。我们通过详细分析错误日志和 Spring 的 Bean 代理机制,逐步排查并解决了这一难题。文章介绍了三种解决方案:调整依赖结构、使用 `@Lazy` 延迟加载以及禁用 `@Async` 的代理功能。希望对你有所帮助!欢迎关注我的微信公众号“软件求生”,获取更多技术干货!



嘿,大家好!我是小米,一个充满活力、喜欢分享技术的29岁开发者。今天的文章,我们要来聊一聊一个发生在我们开发环境的惊险故事。这个问题折腾了我们整个团队好一阵子,最终我们发现元凶竟然是一个看似无害的 @Async 注解。废话不多说,直接开讲!

故事的开始:微服务无法启动

就在昨天,我们的开发环境微服务无法启动,连续尝试了好几次,每次启动的时候日志都报错:

读完这个错误日志,我们开发团队的小伙伴们都挠了挠头,心想这到底是个啥?特别是我们刚刚合并的代码中也没有涉及到什么大改动,为什么会突然冒出这么个问题?

错误日志解析

首先,让我们来分析一下这个错误日志究竟在说什么。

  • Bean 被注入到其他 Beans 中:错误提示说,tradeService 这个 Bean 被注入到了其他两个 Bean(returnOrderServicerefundOrderService)中,这本身其实是没什么问题的。在 Spring 的依赖注入机制下,Bean 被注入到其他 Bean 中是一件再普通不过的事。
  • Raw version 被注入:问题的关键是这里提到的 “raw version”(原始版本)。在 Spring 中,Bean 经过创建和初始化后,可能还会被代理包装。比如,AOP 或者 @Async 会通过动态代理对 Bean 进行增强。那么这里的意思是,tradeService 被注入时,其他服务接收到的不是它的最终版本,而是未经过增强的原始版本。
  • Circular reference(循环引用)问题:这个提示还提到了可能存在的循环引用问题。Spring 为了避免 Bean 的循环依赖,采用了三级缓存机制。在 Bean 创建过程中,如果有 Bean 依赖另一个还未完全初始化完成的 Bean,Spring 会暂时将原始 Bean 注入,这样可以打破循环引用的死锁。
  • “over-eager type matching”:这个错误的最后部分提到了可能的原因:过度积极的类型匹配。在某些情况下,Spring 容器会过早地初始化某些 Bean,这可能会导致一些尚未完全准备好的 Bean 被注入。

至此,我们已经大致明白问题的轮廓:tradeService 这个 Bean 因为某种原因,被注入了它的原始版本,而不是最终被代理增强后的版本。

团队排查:@Async 的可疑之处

我们开始逐步排查代码变更,最后注意到一个开发同事在 tradeService 的某个方法上添加了 @Async 注解。大家知道,@Async 是 Spring 提供的一个非常方便的异步执行注解。通过在方法上加上这个注解,Spring 会把这个方法的执行交给一个线程池,避免占用主线程资源。

虽然 @Async 本身非常好用,但它的实现依赖于 AOP 动态代理机制。当 Spring 看到一个方法被 @Async 注解修饰时,它会创建一个代理对象,代理对象接管方法的调用逻辑。在这种情况下,如果你直接在其他地方引用了这个 Bean 的原始版本,而不是代理后的版本,就会导致预期外的行为——比如像我们遇到的错误。

正是这个 @Async 导致了 Bean 没有被完整初始化,进而引发了循环依赖和代理注入的问题。

深入解析:Spring 的代理机制与循环依赖

为了更好地理解这个问题,我们需要进一步探讨 Spring 是如何处理 Bean 的依赖注入和代理机制。

1. Bean 的创建过程

在 Spring 中,Bean 的创建分为以下几个步骤:

  1. 实例化:首先,Spring 会通过构造函数或者工厂方法实例化 Bean。
  2. 依赖注入:接下来,Spring 会为这个 Bean 注入所需的依赖,这时候如果某个 Bean 依赖还没有准备好,Spring 就会遇到循环依赖问题。
  3. 初始化:在依赖注入完成后,Spring 会执行 Bean 的初始化方法,比如 @PostConstruct 标注的方法,或者执行一些自定义的初始化逻辑。
  4. 代理包装:在初始化完成后,如果这个 Bean 需要增强(如 AOP 或者 @Async),Spring 会为这个 Bean 创建一个代理对象。

2. 循环依赖

如果两个 Bean 相互依赖,比如 A 依赖 B,而 B 又依赖 A,那么 Spring 就会遇到循环依赖问题。为了打破这种循环,Spring 会在依赖注入时注入原始版本的 Bean,而不是最终被代理包装后的版本。

在我们的案例中,tradeServicereturnOrderServicerefundOrderService 引用,而它自身因为使用了 @Async 注解,导致它需要被代理。如果 Spring 在创建代理之前就将它注入到了其他服务中,就会引发前面提到的那个错误。

找到了问题的根源,我们就可以着手解决它了。

方法一:调整依赖结构

最直接的解决方案就是重新审视我们的依赖结构。我们可以考虑是否能打破循环引用,避免 tradeService 被过早地注入到其他 Bean 中。

在我们的案例中,我们尝试通过调整依赖关系,将 tradeService 的一些依赖注入拆分到其他组件中,避免直接注入原始 Bean。

方法二:使用 @Lazy 延迟加载

如果调整依赖结构有些困难,另一种方法是使用 @Lazy 注解。通过将 Bean 设置为懒加载,Spring 会推迟它的实例化,直到它真正被使用时才会创建。这可以有效避免 Bean 在初始化过程中被过早地注入。

方法三:禁用 @Async 的代理

在某些场景下,如果 @Async 只是在某些方法上使用,而不是全局依赖,我们可以考虑不使用代理。在 Spring 中,可以通过配置文件或者编程的方式禁用某些 Bean 的代理行为。

但这种方式比较少见,通常我们会选择保留 @Async 代理,毕竟它为我们提供了异步执行的便利。

END

这次开发环境中的微服务启动问题,最终是由 @Async 引起的代理问题导致的。通过深入理解 Spring 的 Bean 代理机制和循环依赖的处理方法,我们成功找到了问题的根源,并采取了有效的解决方案。这个案例告诉我们,在使用诸如 @Async 这样的注解时,一定要小心其背后的代理机制,尤其是在存在复杂依赖关系的场景下。

如果你也遇到类似的问题,记得从错误日志中寻找线索,逐步排查问题的根源。希望这篇文章对大家有所帮助!

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号软件求生,获取更多技术干货!

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
418 5
|
人工智能 Java 数据库
飞算 JavaAI:革新电商订单系统 Spring Boot 微服务开发
在电商订单系统开发中,传统方式耗时约30天,需应对复杂代码、调试与测试。飞算JavaAI作为一款AI代码生成工具,专注于简化Spring Boot微服务开发。它能根据业务需求自动生成RESTful API、数据库交互及事务管理代码,将开发时间缩短至1小时,效率提升80%。通过减少样板代码编写,提供规范且准确的代码,飞算JavaAI显著降低了开发成本,为软件开发带来革新动力。
|
消息中间件 API 持续交付
后端开发中的微服务架构实践####
【10月更文挑战第21天】 本文深入探讨了微服务架构在后端开发中的应用,从基本概念出发,详细阐述了微服务的核心优势、设计原则及关键技术。通过实际案例分析,揭示了微服务如何助力企业应对复杂业务需求,提升系统的可扩展性、灵活性与可靠性。同时,也指出了实施微服务过程中可能面临的挑战,并提供了相应的解决方案和最佳实践。 ####
237 3
|
10月前
|
IDE Java API
Java 17 新特性与微服务开发的实操指南
本内容涵盖Java 11至Java 17最新特性实战,包括var关键字、字符串增强、模块化系统、Stream API、异步编程、密封类等,并提供图书管理系统实战项目,帮助开发者掌握现代Java开发技巧与工具。
605 1
|
人工智能 搜索推荐 前端开发
从代码到心灵对话:我的CodeBuddy升级体验之旅(个性化推荐微服务系统)
本文分享了使用CodeBuddy最新版本的深度体验,重点探讨了Craft智能体、MCP协议和DeepSeek V3三大功能。Craft实现从对话到代码的无缝转化,大幅提升开发效率;MCP协议打通全流程开发,促进团队协作;DeepSeek V3则将代码补全提升至新境界,显著减少Bug并优化跨语言开发。这些功能共同塑造了AI与程序员共生的未来模式,让编程更高效、自然。
1057 15
|
12月前
|
人工智能 数据可视化 JavaScript
颠覆开发效率!国内首个微服务编排框架Juggle开源啦!
Juggle是国内首个开源的微服务编排框架,专注于解决企业微服务进程中接口重复开发、系统对接复杂等问题。它提供零代码、低代码和AI增强功能,通过可视化拖拽快速组装简单API为复杂接口,支持多协议、多语言脚本和流程多版本管理。相比国外框架如Conductor,Juggle更贴合国内需求,具备高效开发、企业级可靠性及信创适配等优势,助力企业实现敏捷创新与数字化转型。
1004 0
颠覆开发效率!国内首个微服务编排框架Juggle开源啦!
|
11月前
|
Java API 微服务
Java 21 与 Spring Boot 3.2 微服务开发从入门到精通实操指南
《Java 21与Spring Boot 3.2微服务开发实践》摘要: 本文基于Java 21和Spring Boot 3.2最新特性,通过完整代码示例展示了微服务开发全流程。主要内容包括:1) 使用Spring Initializr初始化项目,集成Web、JPA、H2等组件;2) 配置虚拟线程支持高并发;3) 采用记录类优化DTO设计;4) 实现JPA Repository与Stream API数据访问;5) 服务层整合虚拟线程异步处理和结构化并发;6) 构建RESTful API并使用Springdoc生成文档。文中特别演示了虚拟线程配置(@Async)和StructuredTaskSco
1154 0
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——处理系统异常
本文介绍了在Spring Boot项目中如何通过创建`GlobalExceptionHandler`类来全局处理系统异常。通过使用`@ControllerAdvice`注解,可以拦截项目中的各种异常,并结合`@ExceptionHandler`注解针对特定异常(如参数缺失、空指针等)进行定制化处理。文中详细展示了处理参数缺失异常和空指针异常的示例代码,并说明了通过拦截`Exception`父类实现统一异常处理的方法。虽然拦截`Exception`可一劳永逸,但为便于问题排查,建议优先处理常见异常,最后再兜底处理未知异常,确保返回给调用方的信息友好且明确。
1505 0
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——处理系统异常
|
消息中间件 运维 安全
后端开发中的微服务架构实践与挑战####
在数字化转型的浪潮中,微服务架构凭借其高度的灵活性和可扩展性,成为众多企业重构后端系统的首选方案。本文将深入探讨微服务的核心概念、设计原则、关键技术选型及在实际项目实施过程中面临的挑战与解决方案,旨在为开发者提供一套实用的微服务架构落地指南。我们将从理论框架出发,逐步深入至技术细节,最终通过案例分析,揭示如何在复杂业务场景下有效应用微服务,提升系统的整体性能与稳定性。 ####
337 32
|
运维 监控 Java
后端开发中的微服务架构实践与挑战####
在数字化转型加速的今天,微服务架构凭借其高度的灵活性、可扩展性和可维护性,成为众多企业后端系统构建的首选方案。本文深入探讨了微服务架构的核心概念、实施步骤、关键技术考量以及面临的主要挑战,旨在为开发者提供一份实用的实践指南。通过案例分析,揭示微服务在实际项目中的应用效果,并针对常见问题提出解决策略,帮助读者更好地理解和应对微服务架构带来的复杂性与机遇。 ####

热门文章

最新文章