从 new 到容器:后端项目依赖注入的六个落地要点

举报
yd_254101789 发表于 2026/10/26 10:55:43 2026/10/26
【摘要】 在后端项目里,最常见的坏味道之一是业务类里到处 new:Service 里 new Dao,Dao 里 new 数据源,单测时想换一个 Mock 实现,却发现构造链条牵一发而动全身。依赖注入(Dependency Injection,DI)解决的正是这个问题:把“创建依赖”的权力从业务类手里反转出去,交给外部容器。本文总结六个落地要点。 一、反转的是“创建权”,不是“依赖关系”依赖关系依然存...

在后端项目里,最常见的坏味道之一是业务类里到处 new:Service 里 new Dao,Dao 里 new 数据源,单测时想换一个 Mock 实现,却发现构造链条牵一发而动全身。依赖注入(Dependency Injection,DI)解决的正是这个问题:把“创建依赖”的权力从业务类手里反转出去,交给外部容器。本文总结六个落地要点。

一、反转的是“创建权”,不是“依赖关系”

依赖关系依然存在——OrderService 依然需要 PaymentGateway。反转的是谁来创建这个 PaymentGateway。业务类只声明“我需要一个 PaymentGateway”,容器在运行时把具体实现装配进来,业务类永远不自己 new。

二、面向接口是前提

// 依赖抽象,而不是具体类
public class OrderService {
    private final PaymentGateway paymentGateway;

    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

如果 OrderService 直接依赖某个具体支付网关类,就永远失去了替换的可能。接口是 DI 能够成立的前提。

三、三种注入方式,默认用构造器注入

  1. 构造器注入:依赖不可变、必填,对象创建即完整,最利于测试,推荐默认使用。
  2. Setter 注入:适合可选依赖,但对象可能处于“半初始化”状态。
  3. 字段注入 / 接口注入:隐藏了依赖关系,测试困难,不推荐。

Spring 中的典型写法:

@Service
public class OrderService {
    private final PaymentGateway paymentGateway;

    // 单个构造器时 @Autowired 可省略
    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

四、手写一个极简容器,理解 DI 原理

框架做的事情并不神秘,一个极简容器只需“注册表 + 装配”:

public class MiniContainer {
    private final Map<Class<?>, Object> singletons = new HashMap<>();

    public <T> void register(Class<T> type, T instance) {
        singletons.put(type, instance);
    }

    @SuppressWarnings("unchecked")
    public <T> T resolve(Class<T> type) {
        return (T) singletons.get(type);
    }
}

// 使用:组装逻辑集中在容器,业务类只管声明
MiniContainer container = new MiniContainer();
container.register(PaymentGateway.class, new AlipayGateway());
OrderService service = new OrderService(container.resolve(PaymentGateway.class));

Spring、Guice 等框架本质相同,只是多了扫描注册、按依赖图自动装配、管理生命周期等能力。

五、生命周期交给容器

  • 单例(singleton):默认,无状态服务共享一个实例;
  • 原型(prototype):每次获取都新建实例;
  • 请求作用域:Web 场景下每个请求一个实例。

业务代码不再操心何时创建、何时销毁,避免资源浪费和状态错乱。

六、循环依赖:优先重构,延迟注入兜底

A 依赖 B、B 又依赖 A,通常是职责划分不清的信号。优先重构拆分;实在拆不开,再用 Setter 注入或 @Lazy 延迟注入兜底,不要把循环依赖当常态。

结语:测试是检验标准

DI 是否落地成功,看测试就知道。因为依赖都通过构造器传入,单测只需一行:

OrderService service = new OrderService(new MockPaymentGateway());

无需启动整个容器。落地检查清单:

  • [ ] 业务类只依赖接口 / 抽象
  • [ ] 优先使用构造器注入
  • [ ] 容器统一装配,业务代码不直接 new 依赖
  • [ ] 单例为默认,明确需要时才用原型或请求作用域
  • [ ] 出现循环依赖先重构

一句话总结:接口隔离依赖 + 构造器注入声明依赖 + 容器统一装配 = 低耦合、易测试的后端项目。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。