在 Spring 框架中,将一个 Bean 注入到另一个 Bean 里,主要有三种方式。
1. 属性注入 (Field Injection)
这是初学阶段和各种教程中最常见的方式。直接在成员变量上加 @Autowired。
代码示例:
Java
typescript
@RestController
public class UserController {
@Autowired // 核心动作:直接在属性上点名
private UserService userService;
public void list() {
userService.findAll();
}
}
-
优点:代码最简洁,看起来非常干净。
-
缺点:
- 难以脱离容器使用 :如果你想在没有 Spring 环境的情况下(比如写纯 Java 的单元测试)手动 new 这个对象,你没法给
userService赋值。 - 容易循环依赖:如果 A 注入 B,B 又注入 A,这种方式有时会掩盖代码设计的缺陷。
- 难以脱离容器使用 :如果你想在没有 Spring 环境的情况下(比如写纯 Java 的单元测试)手动 new 这个对象,你没法给
2. Setter 方法注入 (Setter Injection)
通过类的 Setter 方法来完成注入。
代码示例:
Java
typescript
@RestController
public class UserController {
private UserService userService;
@Autowired // 核心动作:在 Setter 方法上点名
public void setUserService(UserService userService) {
this.userService = userService;
}
public void list() {
userService.findAll();
}
}
-
优点:比较灵活,可以在对象创建后重新修改注入的实例。
-
缺点:
- 安全性较低:对象在被调用时,可能还没有被注入(比如有人不小心改了值),导致空指针异常。
- 代码冗长:每个属性都要写一套 Setter。
3. 构造器注入 (Constructor Injection) ------ 官方推荐
通过类的构造方法来完成注入。这是 Spring 官方目前最推荐的方式。
代码示例:
Java
java
@RestController
public class UserController {
private final UserService userService; // 甚至可以加 final,保证不可变
// 核心动作:在构造函数上注入(Spring 4.3后,如果只有一个构造函数,@Autowired可省略)
public UserController(UserService userService) {
this.userService = userService;
}
public void list() {
userService.findAll();
}
}
-
优点:
- 保证不为空:对象一出生(构造时)就必须把依赖传进来,否则编译报错。
- 不可变性 :可以使用
final关键字,防止以后被修改。 - 利于测试 :即便没有 Spring,你也可以轻松地通过
new UserController(mockService)来进行测试。
-
缺点:当依赖非常多(比如有 10 个)时,构造函数的参数列表会非常长。
总结:面试怎么答?
| 方式 | 推荐程度 | 理由 |
|---|---|---|
| 构造器注入 | ⭐⭐⭐⭐⭐ | 官方推荐,安全、可靠、方便测试,保证对象状态完整。 |
| 属性注入 | ⭐⭐⭐ | 开发快,代码省事,但在大型项目和严格测试中不推荐。 |
| Setter 注入 | ⭐⭐ | 除非需要在运行期间动态改变依赖,否则很少使用。 |