对象居然不用自己创建?Spring 依赖注入机制的真相惊呆了!

原文来自于:zha-ge.cn/java/123

对象居然不用自己创建?Spring 依赖注入机制的真相惊呆了!

刚学 Java 的时候,我写的每一行代码都充满了 new

ini 复制代码
OrderService orderService = new OrderService();
UserRepository userRepository = new UserRepository();

我以为这就是"面向对象"的正确打开方式,直到我第一次接触 Spring,发现我根本不用 new,对象就神奇地出现了!

那一刻我直接惊呆了:

"我啥时候 new 的?它怎么就有了?我都没创建对象啊!"

其实,这就是 Spring 最核心、也最"神奇"的能力之一------依赖注入(Dependency Injection, DI) 。如果说 IoC(控制反转)是 Spring 的灵魂,那 DI 就是它的手脚。


从"自己 new"到"别人塞给你":控制权反转了

还记得传统写法吗?对象的创建和依赖关系我们自己来搞定:

java 复制代码
public class OrderService {
    private UserRepository userRepository = new UserRepository();
}

这种写法的问题是:类与类强耦合OrderService 一辈子只能用这个版本的 UserRepository,扩展和测试都不灵活。

而在 Spring 世界里,写法变成了这样:

kotlin 复制代码
@Service
public class OrderService {
    @Autowired
    private UserRepository userRepository;
}

没了 new,却依然能用。为什么?

因为控制权已经反转(IoC) :对象的创建不再由你自己掌控,而是交给了 Spring 容器,它会在恰当的时候,把你需要的对象"塞进来" ------这就是依赖注入。


IoC 是理念,DI 是落地方式

经常有人搞混 IoC 和 DI 的关系:

  • IoC(Inversion of Control) 是一个设计思想,强调"谁来控制对象的创建和依赖"。
  • DI(Dependency Injection) 是 IoC 的实现方式,通过"注入"把依赖交给容器完成。

换句话说:

👉 IoC 是"让别人来帮你造对象"的哲学;

👉 DI 是"别人怎么给你塞对象"的技术手段。


三种常见的依赖注入方式

Spring 支持多种注入方式,不同写法背后原理一样,但场景略有不同👇


1. 构造器注入(推荐 ✅)

kotlin 复制代码
@Service
public class OrderService {
    private final UserRepository userRepository;

    @Autowired
    public OrderService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

📌 优点:

  • 对象创建时依赖就确定了,不可能出现"空对象"
  • 更利于单元测试和不可变设计

2. Setter 注入(适合可选依赖)

typescript 复制代码
@Service
public class OrderService {
    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

📌 优点:依赖可以动态修改,适合可选注入

📌 缺点:对象创建后依赖才注入,存在被"忘记"的风险


3. 字段注入(最常见但不推荐 ❌)

kotlin 复制代码
@Service
public class OrderService {
    @Autowired
    private UserRepository userRepository;
}

📌 优点:写法最简洁

📌 缺点:可测试性差,不利于解耦,违反了"依赖显式化"的设计原则


DI 背后的"魔法":反射 + Bean 容器

Spring 到底是怎么把对象塞进去的?其实原理很朴素👇:

  1. Bean 定义加载:Spring 启动时会扫描注解、解析配置,记录有哪些 Bean。
  2. Bean 实例化 :通过反射 Class.newInstance() 创建对象实例。
  3. 依赖解析 :发现字段或构造器参数上有 @Autowired,就去容器里找匹配的 Bean。
  4. 依赖注入 :用反射(Field.set() / 调用构造器)把 Bean 塞进目标类。
  5. 后置处理 & 生命周期回调:Bean 初始化完成后再进行 AOP 增强等。

你写的 @Autowired 只是一个声明,幕后 Spring 已经在拼命用反射和容器做"搬运工"了。


踩坑瞬间:DI 失效的 3 个经典原因

  1. 自己 new 的对象注不进去

    你手动 new 出来的对象不在 Spring 容器里,容器自然不会管它。

    ✅ 解决:交给 Spring 管理,用 @Component 或在配置类中用 @Bean 注册。

  2. Bean 名称或类型冲突

    如果有多个相同类型的 Bean,Spring 不知道注入谁,会抛出异常。

    ✅ 解决:用 @Qualifier("beanName") 明确指定。

  3. 循环依赖

    A 依赖 B,B 又依赖 A,容器可能注不进去。

    ✅ 解决:使用 Setter 注入或 @Lazy 延迟加载来打破死循环。


面试官杀手锏回答

问:Spring 的依赖注入是怎么实现的?

建议你这样答👇:

  • Spring 容器负责对象的创建、管理和依赖注入,实现了控制反转(IoC)。
  • 依赖注入有三种方式:构造器注入、Setter 注入和字段注入。
  • Spring 启动时会扫描 Bean、创建实例,并通过反射将依赖注入到目标 Bean 中。
  • @Autowired 只是声明,真正注入依赖的是容器的 BeanFactory / ApplicationContext。
  • DI 的核心价值是解耦,让对象不再依赖具体实现,易于扩展和测试。

写在最后:从"自己造"到"别人给",是架构的分水岭

当你开始习惯用依赖注入的时候,你就从"写代码"跨进了"设计系统"的门槛。

DI 带来的不是偷懒,而是彻底的解耦:

  • 业务逻辑和对象创建分离
  • 扩展和替换几乎不需要改动代码
  • 单元测试不再依赖真实实现

一句话记住:

依赖注入不是省事的语法糖,而是让代码变得可维护、可测试、可扩展的根本武器。

相关推荐
绝无仅有4 小时前
面试真实经历某商银行大厂Java问题和答案总结(七)
后端·面试·github
勇敢di牛牛4 小时前
Vue+mockjs+Axios 案例实践
前端·javascript·vue.js
绝无仅有4 小时前
面试真实经历某商银行大厂缓存Redis问题和答案总结(一)
后端·面试·github
Rhys..5 小时前
JS - npm init
开发语言·javascript·npm
我是华为OD~HR~栗栗呀6 小时前
华为OD-21届考研-Java面经
java·前端·c++·python·华为od·华为·面试
程序0076 小时前
HTML+JS+CSS实现汽车官网
javascript·css·html
没有鸡汤吃不下饭6 小时前
H5移动端页面实现快递单号条形码/二维码扫描,亲测可行!!
前端·javascript·vue.js
云枫晖6 小时前
JS核心知识-模块化
前端·javascript
Asort6 小时前
JavaScript设计模式(十五)——解释器模式 (Interpreter)
前端·javascript·设计模式