在项目开展历程里, 依赖注入属于核心基础关键能力, 并且@注解差不多是多数从事开发工作者的“默认性选择”。然而, 有不少从事开发工作者在项目施行升级操作、多项Bean之间进行匹配情形、循环依赖问题展开排查过程中, 频繁地遭遇困境, 甚至因为这些状况而引发出生产环节故障 —— 为何平常经常会用到的@竟然会变成技术层面潜在危险性? 且官方最终真正所推荐的依赖注入具体方案究竟是什么? 这是每一位从事Java后端开发工作的人员都必然需要切实理清的关键重要问题。
@ 引发的技术痛点
跟着, 框架进行迭代升级, 特别是在Boot 2.6版本往后, @注解的局限性渐渐显现出来, 于实际项目里引发了许多技术方面的问题:
循环依赖隐患
旧的版本, 能够借助三级缓存去处理, 针对 @ 字段注入所产生的作用, 是循环依赖, 然而, Boot 2.6 以及高于 2.6 的版本, 在默认的情况下, 是将循环依赖允许配置给禁用掉了的。
在.main.allow 被设置为 false 的情况下, 那些大量运用 @ 字段注入的项目, 在升级之后直接启动就会失败。举例来说, 像订单服务与支付服务之间存在的双向 @ 依赖, 具体表现为在新版本当中会直接引发 Bean 创建时的循环依赖报错, 进而阻碍应用的启动。
多 Bean 匹配歧义
@默认依照类型进行注入, 在接口存有多个实现类 Bean 的情形下, 将会抛出。
就在这个时候, 开发者需要另外搭配上@注解以此来指定 Bean 名称, 这不但增加了代码的冗余成分, 而且还提高了维护所需的成本。比如说在支付接口同时存在支付宝、微信支付实现的情况下, 单纯的@是没有办法精确匹配目标 Bean 的。
测试与可维护性短板
@字段注入的Bean不能经由构造器直接进行初始化, 在单元测试的时候要依赖@等特殊注解, 并且有反射注入的局限性;与此同时, 字段级别的依赖注入致使类的依赖关系不透明,代码可读性以及可维护性大幅下滑。
依据官方文档的阐述能够获知其态度, “尽管能够运用@实施注入, 然而构造器注入一般而言更具可取性, 因为它可保证依赖项是能够使用的并且是不可改变的”, 这样的一种表述实际上是在暗示@并非是最佳的解决办法。
依赖注入的分层选型策略
结合官方所制定的规范以及实际操作过程中积累的实战经验, 能够采用一种分层依赖注入方案, 便是以构造器注入作为主要的方式, 将之作为主体, 而对于@则作为辅助手段, 对其使用要谨慎, 谨慎对待, 具体情况如下:
核心依赖:优先构造器注入
对于类的核心业务所依赖的部分(像订单服务依赖的仓储层、支付工厂类这样的情况), 应当采用构造器注入这种方式。这种方式能够在编译期的时候就将循环依赖问题暴露出来, 与此同时还支持使用final字段进行修饰, 以此保证依赖是不可变的, 并且也方便在单元测试的时候直接借助构造器来Mock依赖。示例代码如下:
@Service public class OrderService { // 核心依赖通过构造器注入,保证不可变 private final OrderRepository orderRepository; private final PaymentServiceFactory paymentServiceFactory; public OrderService(OrderRepository orderRepository, PaymentServiceFactory paymentServiceFactory) { this.orderRepository = orderRepository; this.paymentServiceFactory = paymentServiceFactory; } }可选依赖: 针对非核心的那种可选依赖(像通知服务、缓存模板这类), 采用 @ 精准注入, 能够使用 @ 注解, @ 遵循 JSR - 250 标准, 会优先依据名称来注入 Bean, 在没有歧义的情况下不需要额外的注解, 并且注入效率比 @ 要高大概 39.8%(这是基于 10000 个 Bean 的实测数据)。在存在多实现 Bean 的场景当中, @ 能够借助 name 属性直接去指定目标 Bean, 如下是示例:
@RestController @RequestMapping("/api/orders") public class OrderController { // 核心依赖构造器注入 private final OrderService orderService; // 可选依赖@Resource注入,指定Bean名称解决多实现歧义 @Resource(name = "primaryOrderMapper") private OrderMapper orderMapper; @Resource private RedisTemplate redisTemplate; public OrderController(OrderService orderService) { this.orderService = orderService; } }特殊场景:@ 的限定使用
只有在兼容旧系统的情况下, 并且是没有多 Bean 歧义的简单场景内, 才能够少量使用 @, 不过要避免字段注入, 要改用 注入的方式, 与此同时标注=false 来降低强依赖风险。
落地效果与总结
某电商项目曾有过这样的变革, 即从 “全@字段注入” 转变为分层依赖注入的重构操作, 对其进行重构之后, 有诸多数据得以呈现, 具体如下: 应用启动所耗费的时间, 从原本的3.2秒, 缩短到了2.8秒, Bean注入所消耗的时间, 由347毫秒降低至259毫秒, 循环依赖风险, 从 “高“ 的程度降至 “低” 的程度, 单元测试通过率, 提升了30%, 代码的可读性以及可维护性, 有了很明显的改善。
广大Java后端开发者, 要摒弃那种“@万能”的一贯认知, 去遵循官方的依赖注入优先级规范, 依据此在项目里落实“构造器 +@”的分层方案, 而且建议团队在代码里面强化对于依赖注入规范的校验, 以此从源头避免技术方面的隐患。
要是你于依赖注入选型期间碰到过特殊场景下的疑难问题, 又或者有着更优的实践方案, 那么欢迎在评论区留言交流, 一块儿去完善技术栈的最佳实践体系!