当前位置: 欣欣网 > 码农

停止在 SpringBoot 中使用字段注入!

2024-04-01码农

大家好,我是鹏磊。

在 Spring Boot 依赖项注入的上下文中,存在关于注入依赖项最佳实践的争论:字段注入、Setter注入和构造函数注入。

在本文中,我们将重点讨论字段注入的缺陷,并提出一个远离它的案例。

什么是字段注入?

字段注入涉及直接用 @Autowired 注释类的私有字段。这是一个例子:

@Component
public classOrderService{
@Autowired
private OrderRepository orderRepository;
public Order findOrderById(Long id){
return orderRepository.findById(id);
}
}

为什么应该停止使用字段注入

1. 可测试性

字段注入使组件的单元测试变得复杂。 由于依赖项直接注入到字段中,因此我们无法在 Spring 上下文之外轻松提供模拟或替代实现。

让我们以 sameOrderService 类为例。

如果我们希望对 OrderService 进行单元测试,那么在模拟 OrderRepository 时会遇到困难,因为它是一个私有字段。下面是对 OrderService 进行单元测试的方法:

如果你近期准备面试跳槽,建议在ddkk.com在线刷题,涵盖 一万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题,还有市面上最全的技术五百套,精品系列教程,免费提供。

@RunWith(SpringJUnit4 classRunner. class)
public classOrderServiceTest
{
private OrderService orderService;
@Mock
private OrderRepository orderRepository;
@Before
publicvoidsetUp()throws Exception {
orderService = new OrderService();
// This will set the mock orderRepository into orderService's private field
ReflectionTestUtils.setField(orderService, "orderRepository", orderRepository);
}
...
}



尽管可以实现,但使用反射来替换私有字段并不是一个很好的设计。它违背了面向对象的设计原则,使测试难以阅读和维护。

但是,如果我们使用构造函数注入:

@Component
public classOrderService{
privatefinal OrderRepository orderRepository;
publicOrderService(OrderRepository orderRepository){
this.orderRepository = orderRepository;
}
}

我们可以在测试期间轻松提供模拟 OrderRepository:

OrderRepository mockRepo = mock(OrderRepository. class);
OrderService orderService = new OrderService(mockRepo);

2. 不变性

字段注入使我们的 Bean 在构建后可变。而通过构造函数注入,一旦构造了一个对象,它的依赖关系就会保持不变。

举例来说:

字段注入类:

@Component
public classUserService{
@Autowired
private UserRepository userRepository;
}

这里,userRepository 在创建对象后可以重新分配引用,这就打破了不变性原则。

如果我们使用构造函数注入:

@Component
public classUserService{
privatefinal UserRepository userRepository;
publicUserService(UserRepository userRepository){
this.userRepository = userRepository;
}
}

该 userRepository 字段可以声明为最终字段,在构造完成后,就会一直保持不变。

3.与Spring更紧密的耦合

字段注入使我们的类与 Spring 耦合更紧密,因为它直接在我们的字段上使用 Spring 特定的注释 ( @Autowired)。这可能会在以下场景中出现问题:

不使用 Spring 的情况 :假设我们正在构建一个不使用 Spring 的轻量级命令行应用程序,但我们仍然想利用 UserService 的逻辑。在这种情况下,@Autowired 注释没有任何意义,不能用于注入依赖项。我们就必须重构该类或实现繁琐的解决方法才能重用UserService.

如果你近期准备面试跳槽,建议在ddkk.com在线刷题,涵盖 一万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题,还有市面上最全的技术五百套,精品系列教程,免费提供。

切换到另一个 DI 框架 :如果我们决定切换到另一个依赖注入框架,比如 Google Guice,Spring 特定的框架 @Autowired 就会成为一个障碍。那时我们必须重构使用 Spring 特定注释的每一个地方,这会是十分繁琐的。

可读性和理解性 :对于不熟悉 Spring 的开发人员来说,遇到 @Autowired 注解可能会感到困惑。他们可能想知道如何解决依赖关系,从而增加学习成本(ps:虽然不熟悉 Spring 开发的Java程序员可能很少了)。

4. 空指针异常

当类利用字段注入并通过其默认构造函数实例化时,依赖字段保持未初始化。

举例来讲:

@Component
public classPaymentGateway{
@Autowired
private PaymentQueue paymentQueue;
publicvoidinitiate(PaymentRequest request){
paymentQueue.add(request);
...
}
}
public classPaymentService{
publicvoidprocess(PaymentRequest request){
PaymentGateway gateway = new PaymentGateway();
gateway.initiate(request);
}
}

通过上面的代码,我们不难看出,如果在运行时以这种状态访问PaymentGateway,则会发生 NullPointerException。在Spring上下文之外手动初始化这些字段的唯一方法是使用反射,反射机制的语法比较繁琐且易错,在程序可读性方面存在一定问题,所以不建议这样做。

5. 循环依赖

字段注入可能会掩盖循环依赖问题,使它们在开发过程中更难被发现。

举例来讲:

考虑两个相互依赖的服务AService和BService:

@Service
public classAService{
@Autowired
private BService bService;
}
@Service
public classBService{
@Autowired
private AService aService;
}

以上可能会导致应用程序中出现意想不到的问题。

使用构造函数注入,Spring会在启动期间立即抛出 BeanCurrentlyInCreationException,让我们意识到循环依赖。不过,要解决循环依赖问题,可以使用@Lazy延迟加载其中一个依赖项。

结论

虽然字段注入可能看起来更简洁,但它的缺点远远超过了它的简洁性。构造函数注入在应用程序的可测试性、不变性和整体稳健性方面提供了明显的优势。它与 SOLID 原则非常一致,确保我们的 Spring Boot 应用程序可维护且不易出错。

所以,建议大家停止在 Spring Boot 中使用字段注入!

🔥 磊哥私藏精品 热门推荐 🔥