Why field injection is evil

发布于 11 天前  33 次阅读


这篇[https://odrotbohm.de/2013/11/why-field-injection-is-evil/]文章讨论了字段注入(Field Injection)和构造函数注入(Constructor Injection)的优缺点,并提倡使用构造函数注入。以下是文章的主要观点和总结:

字段注入的问题

  1. 容易引发空指针异常(NullpointerExceptions)
    • 字段注入允许客户端创建类的实例而不提供依赖项,这可能导致对象处于无效状态。
    • 示例代码: MyComponent component = new MyComponent(); component.myBusinessMethod(); // -> NullPointerException
  2. 隐藏依赖关系
    • 字段注入隐藏了类的依赖关系,客户端无法通过类的公共接口(如构造函数或方法签名)了解其依赖项。
    • 这使得代码在跨项目共享时难以维护,因为开发者需要通过运行时错误来发现缺失的依赖。
  3. 测试复杂性
    • 字段注入需要通过反射来注入依赖项,这增加了测试的复杂性。
    • 示例代码:MyCollaborator collaborator = ... // mock dependency MyComponent component = new MyComponent(); // 使用反射注入依赖项 component.myBusinessMethod();

构造函数注入的优点

  1. 强制依赖
    • 构造函数注入要求客户端在创建对象时提供所有必要的依赖项,确保对象在创建时处于有效状态。
    • 示例代码:class MyComponent { private final MyCollaborator collaborator; @Inject public MyComponent(MyCollaborator collaborator) { Assert.notNull(collaborator, "MyCollaborator must not be null!"); this.collaborator = collaborator; } public void myBusinessMethod() { collaborator.doSomething(); } }
  2. 清晰的依赖关系
    • 构造函数注入通过类的公共接口(构造函数)明确展示了依赖项,使得代码更具可读性和可维护性。
  3. 便于测试
    • 构造函数注入使得测试更加简单,因为可以直接通过构造函数传递依赖项。
    • 示例代码:MyCollaborator collaborator = ... // mock dependency MyComponent component = new MyComponent(collaborator); component.myBusinessMethod();
  4. 不可变性
    • 使用 final 修饰的字段可以确保依赖项在对象创建后不可变,提高了代码的安全性和可维护性。

字段注入 vs 构造函数注入的对比

表格

复制

特性字段注入(Field Injection)构造函数注入(Constructor Injection)
代码量较少较多(但可以通过工具如 Lombok 减少)
安全性不安全(容易引发空指针异常)安全(确保对象处于有效状态)
依赖关系隐藏明确
测试复杂性复杂(需要反射)简单(直接通过构造函数传递依赖项)

辅助工具

  • *Lombok:可以减少构造函数注入所需的样板代码。例如,使用 @RequiredArgsConstructor 自动生成构造函数:

@RequiredArgsConstructor(onConstructor = @__(@Inject))

class MyComponent {

private final @NonNull MyCollaborator collaborator;

public void myBusinessMethod() {

collaborator.doSomething();

}

}

总结

  • 字段注入虽然减少了代码量,但容易引发空指针异常,隐藏依赖关系,并增加了测试的复杂性。
  • 构造函数注入虽然需要编写更多代码,但提供了更高的安全性、明确的依赖关系和更好的测试支持。
  • 使用工具如 Lombok 可以减少构造函数注入的样板代码,同时保留其优点。