知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!
Spring 不推荐使用基于字段的依赖注入(Field Injection),主要有以下几个原因:
1. 破坏不可变性(Immutability)
-
字段注入无法声明
final
字段
构造器注入允许将依赖字段声明为final
,确保对象一旦创建,依赖关系不可变。而字段注入的字段必须是可变的(非final
),这在设计上降低了安全性和线程安全性。java// 构造器注入:依赖不可变 private final UserService userService; public UserController(UserService userService) { this.userService = userService; } // 字段注入:依赖可变(非 final) @Autowired private UserService userService;
2. 隐藏依赖关系,降低代码可读性
-
依赖不透明
字段注入的依赖关系分散在类的各个字段中,难以通过类的公共接口(如构造函数或 Setter 方法)直观看到所有依赖。而构造器注入通过参数列表明确声明所有必需依赖,代码更易维护。java// 构造器注入:依赖关系一目了然 public OrderService(PaymentService paymentService, InventoryService inventoryService) { // ... } // 字段注入:需遍历类中的 @Autowired 字段才能发现依赖 @Autowired private PaymentService paymentService; @Autowired private InventoryService inventoryService;
3. 增加与容器的耦合性
-
无法脱离 Spring 容器创建对象
字段注入依赖反射机制,若直接通过new
关键字实例化对象,依赖不会被自动注入,导致对象处于不完整状态。而构造器注入允许手动传入依赖,脱离容器也能正常工作。java// 构造器注入:可手动传入依赖 UserService userService = new UserServiceImpl(); UserController controller = new UserController(userService); // 有效 // 字段注入:直接 new 对象时依赖为 null UserController controller = new UserController(); // userService = null
4. 测试困难
-
依赖注入需要容器或反射
单元测试时,字段注入的对象需要通过反射(如ReflectionTestUtils
)或依赖 Spring 容器(如@SpringBootTest
)才能注入 Mock 对象。构造器注入则可通过普通 Java 代码直接传递 Mock 依赖。java// 构造器注入:直接传递 Mock 对象 @Test void testController() { UserService mockService = Mockito.mock(UserService.class); UserController controller = new UserController(mockService); // ... } // 字段注入:需通过反射设置私有字段 @Test void testController() { UserController controller = new UserController(); ReflectionTestUtils.setField(controller, "userService", Mockito.mock(UserService.class)); // ... }
5. 循环依赖问题
-
字段注入可能掩盖设计缺陷
构造器注入在存在循环依赖时会直接抛出BeanCurrentlyInCreationException
,迫使开发者重新设计代码以消除循环依赖。而字段注入(或 Setter 注入)允许 Spring 通过三级缓存解决循环依赖,可能导致开发者忽略代码设计问题。java// 构造器注入:直接暴露循环依赖 // 抛出异常:Requested bean is currently in creation public class A { public A(B b) { /* ... */ } } public class B { public B(A a) { /* ... */ } } // 字段注入:Spring 通过三级缓存解决循环依赖(但设计不合理) public class A { @Autowired private B b; } public class B { @Autowired private A a; }
6. 违反单一职责原则
- 过多的字段注入可能掩盖类的臃肿
构造器注入的参数列表过长时,会直观提示类可能承担了过多职责(如超过 3-4 个依赖),促使开发者重构代码。而字段注入的分散注解可能掩盖这一问题。
官方建议与演进趋势
-
Spring 官方推荐
自 Spring Framework 4.x 起,官方文档推荐优先使用 构造器注入 (强制依赖)和 Setter 注入(可选依赖),明确表示字段注入是"不推荐的"(less favored)。 -
Spring Boot 的隐式支持
从 Spring Boot 2.x 开始,如果类仅有一个构造器,Spring 会默认选择它进行依赖注入,无需显式添加@Autowired
,进一步鼓励构造器注入。java// Spring Boot 自动注入唯一构造器 public class UserController { private final UserService userService; public UserController(UserService userService) { // 自动注入 this.userService = userService; } }
适用场景与例外
尽管字段注入存在诸多问题,但在以下场景中可能被容忍:
- 原型验证或快速开发
快速编写临时代码时,字段注入的简洁性可能优先于设计规范。 - 框架或库的兼容性
某些框架(如 JPA 的@PersistenceContext
)可能强制使用字段注入。 - 遗留代码维护
修改旧代码时,若重构成本过高,可暂时保留字段注入。
总结
依赖注入方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
构造器注入 | 依赖不可变、代码透明、易测试 | 参数较多时代码冗长 | 强制依赖、核心业务逻辑 |
Setter 注入 | 支持可选依赖、灵活性高 | 无法保证依赖完整性 | 可选依赖、动态配置更新 |
字段注入 | 代码简洁 | 隐藏依赖、破坏不可变性、测试困难 | 临时代码、特定框架需求 |