当我们在谈论构造函数注入的时候我们在谈论什么 | 京东物流技术团队

京东云开发者
• 阅读 133

依赖注入

当涉及依赖注入(Dependency Injection,DI)时,首先推荐使用构造函数注入,因为构造函数注入有很多技术优点,而且还与面向对象的设计原则密切相关。在业界,构造函数注入作为依赖注入的一种最佳实践得到了广泛的认可,在Spring Framework的作者之一Rod Johnson的观点中也得有体现。

下面是Spring官方文档中对于依赖注入的描述:

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Autowired annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.

The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of t hat class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection. ——Spring官网原文链接 https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html

在本文中,我们将更深入地探讨为何构造函数注入被认为是最佳实践,并将通过详细的Java代码示例来阐明其优点。同时,我们将研究如何将构造函数注入与面向对象的设计理念相结合,特别是如何确保封装、单一责任、不变性和依赖倒置原则得以遵循。

有哪些优势

构造函数注入的重要性

依赖注入是一种关键的技术,可以提高应用程序的可测试性和可维护性。Rod Johnson在他的书中明确指出,通过将依赖项注入到对象中,可以更轻松地进行单元测试,同时降低了对象之间的耦合度。这正是构造函数注入所实现的。当我们在对象的构造函数中传递依赖项时,我们不仅提供了明确的依赖关系,还提高了代码的清晰度。让我们通过一个示例来看看构造函数注入的工作方式。

public class OrderService {
    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    // ...
}

在上述示例中,OrderService的构造函数接受一个OrderRepository作为参数,明确指定了其依赖关系。这不仅提高了代码的可读性,还使得单元测试变得更加容易。您可以轻松地创建一个模拟的OrderRepository并将其传递给OrderService的构造函数,以进行单元测试。

构造函数注入与封装特性

面向对象编程强调封装特性,即将数据和行为封装在类的内部,通过公共接口来访问对象。构造函数注入有助于维护封装特性,因为它允许您在对象内部设置依赖项,而不需要向外部暴露setter方法。这符合依赖倒置原则和接口隔离原则的思想。

通过将依赖项作为构造函数参数传递,您确保了依赖项在对象内部得到了封装。这意味着外部代码无法直接修改对象的依赖项,从而提高了代码的安全性和稳定性。让我们来看一个例子:

public class CustomerService {
    private final CustomerRepository customerRepository;

    public CustomerService(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    public Customer getCustomerById(int customerId) {
        return customerRepository.findById(customerId);
    }
    // ...
}

在上面的示例中,CustomerService依赖于CustomerRepository,并通过构造函数注入的方式获得了该依赖。这确保了customerRepository的封装性,不允许外部代码直接访问或修改它。

构造函数注入与单一责任原则

单一责任原则是面向对象设计的基本原则之一,强调一个类应该只有一个理由去改变。构造函数注入有助于实现这一原则,因为它鼓励每个类专注于执行单一任务,而不负责创建或管理依赖项。通过使用构造函数注入,您可以将依赖项的创建和配置从类中分离出来,使每个类专注于自身的主要职责。这提高了代码的模块化性和可维护性。以下是一个示例:

public class ProductService {
    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public List<Product> getAllProducts() {
        return productRepository.getAll();
    }

    // ...
}

在上述示例中,ProductService专注于处理产品相关的业务逻辑,而不需要关心如何创建或配置ProductRepository。这遵循了单一责任原则,使代码更加清晰和可维护。

构造函数注入与不变性

构造函数注入还有助于防止不必要的可变性,因为一旦依赖项被设置,它们通常是不可变的。不可变对象在面向对象设计中具有重要地位,因为它们更容易理解和维护。通过将依赖项注入到对象中并在构造函数中进行初始化,您可以确保依赖项在对象的整个生命周期内保持不变。这有助于减少对象状态的变化,从而提高了代码的可维护性和可预测性。

构造函数注入与依赖注入容器

构造函数注入与依赖注入容器(如Spring容器)协同工作得很好。您可以使用构造函数注入来定义组件的依赖关系,并让容器负责创建和管理对象的生命周期。

@Component
public class AppConfig {
    @Bean
    public OrderRepository orderRepository() {
        return new JpaOrderRepository();
    }

    @Bean
    public OrderService orderService(OrderRepository orderRepository) {
        return new OrderService(orderRepository);
    }
}

在上述示例中,我们使用Spring的Java配置来定义OrderRepositoryOrderService之间的依赖关系,并通过构造函数注入实现了依赖解析。

构造函数注入与单元测试

构造函数注入使得编写单元测试变得更容易,因为您可以轻松地传递模拟或测试用的依赖项到对象的构造函数中。这样,您可以在不依赖于容器或其他复杂配置的情况下,对类进行单元测试。

public class OrderServiceTest {
    @Test
    public void testCalculateTotalPrice() {
        OrderRepository mockRepository = mock(OrderRepository.class);
        when(mockRepository.findOrderById(1)).thenReturn(new Order(1, 100.0));

        OrderService orderService = new OrderService(mockRepository);

        double totalPrice = orderService.calculateTotalPrice(1);
        assertEquals(100.0, totalPrice, 0.01);
    }
}

上述单元测试中,我们使用构造函数注入创建了一个OrderService的实例,并注入了一个Mock的OrderRepository

总结

通过以上示例,阐述了构造函数注入在依赖注入中的价值,以及它如何与面向对象的设计原则协同工作。这不仅提高了代码的可维护性和可测试性,还使其更符合面向对象设计的最佳实践。构造函数注入作为一种强大的工具,有助于构建高质量、可维护和可测试的应用程序。希望通过本文,您能更深入地了解构造函数注入的价值和实践。

参考

https://www.baeldung.com/constructor-injection-in-spring

https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html

作者:京东物流 张涛

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

点赞
收藏
评论区
推荐文章
Easter79 Easter79
2年前
spring核心思想:IOC(控制反转)、DI(依赖注入)和AOP(面向切面编程)
Spring有三大核心思想,分别是控制反转(IOC,InversionOfController),依赖注入(DI,DependencyInjection)和面向切面编程(AOP,AspectOrientedProgramming)。控制反转(IOC,InversionOfController)控制反转不是什么技术,而是一种设计思
Easter79 Easter79
2年前
SpringIOC官方文档解读
IoC容器本章介绍了Spring的控制反转(IoC)容器。1.1。SpringIoC容器和Bean简介本章介绍了反转控制(IoC)原则的Spring框架实现。IoC也称为依赖注入(DI)。在此过程中,对象可以通过①构造函数参数(),②工厂方法的参数③或在构造或从工厂方法返回后在对象实例上设置的属性来定义其依
Easter79 Easter79
2年前
SpringBoot基础篇Bean之基本定义与使用
更多Spring文章,欢迎点击一灰灰BlogSpring专题(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fspring.hhui.top)我们知道在Spring中,有两个非常有名的特性,依赖注入(DI)与切面(AOP),其中依赖注入其主要的作用,可以说就是维护Spring容
Wesley13 Wesley13
2年前
3、Angular JS 学习笔记 – Controllers [翻译中]
理解控制器在Angular中,一个控制器是一个javascript构造函数用于填充Angular作用域。当一个控制器通过使用ngcontroller指令附加到DOM上的时候,Angular将初始化一个新的Controller对象,使用指定的控制器构造函数。一个新的子作用域将可以作为一个参数$scope被注入到控制器构造函数。控制器用
Easter79 Easter79
2年前
Spring的IOC逐层深入——依赖注入的两种实现类型(四)
构造器注入    构造器注入,即通过构造函数完成依赖关系的设定。我们看一下spring的配置文件:\html\ viewplain(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fblog.csdn.net%2Fjiuqiyuliang%2Farti
Stella981 Stella981
2年前
Android依赖注入应用
依赖注入(DI)是一种设计模式,允许在运行时或编译时移除或改变硬编码的依赖性。使用依赖注入库可以减少编码量、把精力专注在更有价值的地方、降低维护成本。Android程序通常使用注解(Annotation,例如@Click)实现“声明式编程”和依赖注入。注:“声明式编程”告诉机器在什么地方做什么事(Where
Easter79 Easter79
2年前
Spring常用的三种注入方式
Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入,setter注入,基于注解的注入。构造方法注入先简单看一下测试项目的结构,用maven构建的,四个包:entity:存储实体,里面只有一个User类dao:数据访问,一个接口,两个实现类service:服务层,一个接口,一个
Easter79 Easter79
2年前
Spring两种依赖注入方式的比较
我们知道,Spring对象属性的注入方式有两种:设值注入和构造注入。先看代码:  假设有个类为People,该对象包含三个属性,name和school还有age,这些属性都有各自的setter和getter方法,还有一个包含这三个属性的构造方法。如果用spring来管理这个对象,那么有以下两种方式为People设置属性:  1.设值注入:
Wesley13 Wesley13
2年前
C#中依赖注入
由于客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户只定义一个注入点。在程序运行过程中,客户类部直接实例化具体服务类实例,而是客户类的运行上下文环境或者专门组建负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。依赖注入的方法大致分为3中:接口注入、构造方法注入、setter注入;1、setter注入setter注入是指
Wesley13 Wesley13
2年前
Spring方法注入
在spring中注入方式有3中:1,构造函数注入2,set方法注入3,接口注入(方法注入)在spring中的bean默认范围都是单例,但是在特定的情况下,我们需要有如下的业务需要,单例bean1需要依赖非单例bean2,由于bean1始终是单例,所以如果不做出改变,每次获取的bean2也是同一个,容器就没办法给我们提供