一、一句话定义
| 方式 | 核心特征 | 通俗理解 |
|---|---|---|
| 静态依赖注入 | Spring 自动把依赖塞进来 | 食堂阿姨给你打饭,你坐着等就行 |
| 动态依赖查找 | 你主动去容器里找 Bean | 自助餐,你自己起身去拿 |
二、静态依赖注入(Static Dependency Injection)
代码长这样
java
@Service
public class OrderService {
// 方式1:字段注入(最常见)
@Autowired
private UserService userService;
// 方式2:构造方法注入(Spring 推荐)
public OrderService(UserService userService) {
this.userService = userService;
}
// 方式3:Setter 注入
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
特点
-
启动时就定死了:Spring 扫描到
@Autowired,启动阶段就会把UserService的对象注入进来。 -
你完全不用管:依赖怎么来的、什么时候创建的,你都不关心。
-
依赖关系明确 :看一眼代码,就知道
OrderService依赖UserService。
三、动态依赖查找(Dynamic Dependency Lookup)
代码长这样
java
@Service
public class OrderService implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext ctx) {
this.context = ctx;
}
public void createOrder(String type) {
// 运行时,根据参数动态去容器里"查找"对应的 Bean
PaymentStrategy strategy = context.getBean(type + "Strategy", PaymentStrategy.class);
strategy.pay();
}
}
或者更直接的:
java
@Autowired
private ApplicationContext context;
public void doSomething() {
// 需要的时候才去"拿"
UserService userService = context.getBean(UserService.class);
}
特点
-
运行时才能确定:代码写的时候不知道要拿哪个 Bean,运行时才决定。
-
你主动伸手 :不是 Spring 塞给你,是你主动
getBean()。 -
耦合了 Spring 容器 :你的代码里出现了
ApplicationContext,离开 Spring 框架这段代码就没法跑了。
四、核心区别对比
| 对比项 | 静态依赖注入 | 动态依赖查找 |
|---|---|---|
| 代码写法 | @Autowired、构造方法 |
context.getBean(...) |
| 时机 | 容器启动时注入 | 运行时按需查找 |
| 依赖是否明确 | 明确,写死在代码里 | 不明确,运行时动态决定 |
| 是否耦合 Spring | 不耦合(注解可被其他框架识别) | 强耦合 ApplicationContext |
| 灵活性 | 低,依赖固定 | 高,可以动态切换 |
| 可测试性 | 好,直接 Mock 依赖 | 差,需要模拟 Spring 容器 |
| 典型场景 | 绝大多数日常开发 | 策略模式、插件化、工厂模式 |
五、为什么需要"动态依赖查找"?
你可能会问:静态注入这么好用,为什么还要主动 getBean()?
场景 1:策略模式(运行时决定用哪个实现)
java
@Service
public class PayFactory implements ApplicationContextAware {
private ApplicationContext context;
public void pay(String channel) { // channel = "alipay" 或 "wechat"
// 启动时不知道用户会选哪种支付方式,只能运行时去查
PaymentService service = context.getBean(channel + "PayService", PaymentService.class);
service.pay();
}
}
这里你不可能 写 @Autowired,因为启动时不知道用户会传 "alipay" 还是 "wechat"。
场景 2:解决循环依赖(应急方案)
java
@Service
public class A {
@Autowired
private ApplicationContext context;
// 构造方法注入会导致循环依赖时,可以延迟查找
public void doSomething() {
B b = context.getBean(B.class); // 用的时候才拿,打破循环
b.doSomething();
}
}
场景 3:获取非单例 Bean(Prototype)
// 每次都需要一个新的对象
TaskExecutor executor = context.getBean(TaskExecutor.class);
六、面试常问:ApplicationContextAware 属于哪种?
标准答案:
ApplicationContextAware本身是 Aware 接口回调 (属于生命周期钩子),但它赋予了你"动态依赖查找"的能力。
也就是说:
-
实现
ApplicationContextAware→ 是为了拿到 Spring 容器 -
拿到容器后调用
getBean()→ 这就是动态依赖查找
面试官追问 :既然能动态查找,是不是所有依赖都用 getBean() 更好?
答 :不是。能用静态注入的,绝对不要用动态查找。因为:
-
代码耦合 Spring 容器,不好单元测试
-
隐藏了类的依赖关系,代码可读性差
-
违背了"控制反转"的初衷(IoC 就是让你被动接收,而不是主动索取)
七、一张图记住
【Spring 容器】
│
├─ 静态注入 ──→ Spring 主动推给你 ──→ @Autowired
│ "你坐着,饭送嘴边"
│
└─ 动态查找 ──→ 你主动找容器要 ──→ getBean()
"你自己去窗口端菜"
八、总结
| 你的情况 | 用哪个 |
|---|---|
| 依赖关系明确,启动时就知道要谁 | 静态注入 @Autowired |
| 运行时才能确定要哪个 Bean | 动态查找 getBean() |
| 写框架、做插件化、策略工厂 | 可以用动态查找 |
| 写普通业务代码 | 只用静态注入,别碰 getBean() |
记住一个原则:动态查找是"杀手锏",不是"日常武器"。 日常开发里看到有人到处 getBean(),通常说明代码设计有问题。