首页 > 其他分享 >控制反转(IoC)与依赖注入(DI)

控制反转(IoC)与依赖注入(DI)

时间:2024-12-04 23:02:00浏览次数:6  
标签:依赖 DI 对象 反转 UserRepository OrderService IoC 注入

文章目录


前言

学习 IoC 和 DI 主要有两个重要原因。

一是优化软件架构,在复杂系统中,组件间依赖关系复杂,硬编码会导致紧密耦合。IoC 和 DI 通过外部容器管理依赖,让组件专注自身业务,降低耦合度,方便系统维护和扩展。

二是提高代码可测试性,单元测试需要隔离外部依赖,没有 IoC 和 DI 时,很难模拟依赖行为,而它们可以方便地在测试中注入模拟依赖,更好地测试业务逻辑。


一、控制反转(IoC)

将对象的控制权从应用程序代码转移到外部容器。

如何理解控制反转以及它的作用呢,当然是看看如果没有控制反转,会怎么样。

1.如果没有控制反转,会怎么样?

在传统的面向对象编程中,对象之间的依赖关系是通过硬编码的方式建立的。

以一个简单的电子商务系统为例,假设有一个OrderService类用于处理订单相关的业务逻辑,它需要访问数据库来获取商品信息、用户信息以及保存订单记录。在没有 IoC 的情况下,OrderService可能会直接在其内部实例化数据库访问对象,如下所示:

class OrderService {
    private ProductRepository productRepo = new ProductRepositoryImpl();
    private UserRepository userRepo = new UserRepositoryImpl();
    private OrderRepository orderRepo = new OrderRepositoryImpl();

    public void placeOrder(Order order) {
        User user = userRepo.getUser(order.getUserId());
        List<Product> products = productRepo.getProducts(order.getProductIds());
        // 处理订单逻辑,如计算总价、验证库存等
        orderRepo.saveOrder(order);
    }
}

OrderService直接负责创建ProductRepository、UserRepository和OrderRepository的实例,那么可能会导致什么问题呢?

1.1紧密耦合:

OrderService与这三个仓库类紧密耦合在一起。如果需要更换数据库访问(如从关系型数据库切换到非关系型数据库),或者对仓库类的实现进行修改(如修改构造函数、添加新的方法参数),那么OrderService的代码也必须随之修改。

1.2可测试性差:

在对OrderService进行单元测试时,很难隔离它与数据库访问对象的依赖关系。因为这些依赖对象是在OrderService内部创建的,测试代码无法轻易地替换为模拟对象,从而导致测试变得复杂。

1.3代码复用性受限:

由于OrderService与特定的仓库类实现紧密绑定,很难在其他场景下复用OrderService,除非新场景也使用完全相同的仓库类实现。


2. IoC 的核心思想与实现机制

2.1核心思想:

IoC 的核心思想是将对象的控制权从应用程序代码转移到外部容器。就像一个工厂(容器)负责生产各种零件(对象),并按照一定的规则(配置)将它们组装(管理依赖关系)起来,而使用这些零件的机器(应用程序中的其他对象)只需要关心如何使用这些零件来完成自己的工作,而不需要关心零件是如何生产和组装的。


2.2实现机制 - 配置驱动:

2.2.1XML 配置方式:

Spring 框架可以通过 XML 文件来配置对象之间的关系。在上述电子商务系统的例子中,可以创建一个application - context.xml文件来配置OrderService及其依赖的仓库类:

<?xml version="1.0" encoding="UTF - 8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema - instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring - beans.xsd">
    <bean id="productRepository" class="com.example.repository.ProductRepositoryImpl"/>
    <bean id="userRepository" class="com.example.repository.UserRepositoryImpl"/>
    <bean id="orderRepository" class="com.example.repository.OrderRepositoryImpl"/>
    <bean id="orderService" class="com.example.service.OrderService">
        <constructor - arg ref="productRepository"/>
        <constructor - arg ref="userRepository"/>
        <constructor - arg ref="orderRepository"/>
    </bean>
</beans>

在这个配置文件中,首先定义了三个仓库类的 Bean,然后定义了OrderService的 Bean,并通过构造函数注入的方式将三个仓库类的 Bean 注入到OrderService中。Spring 的 IoC 容器会读取这个配置文件,根据配置信息创建对象并管理它们之间的关系。

2.2.2Java 配置方式:

除了 XML 配置,还可以使用 Java 类来进行配置。创建一个AppConfig类,如下所示:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public ProductRepository productRepository() {
        return new ProductRepositoryImpl();
    }

    @Bean
    public UserRepository userRepository() {
        return new UserRepositoryImpl();
    }

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

    @Bean
    public OrderService orderService(ProductRepository productRepository,
                                     UserRepository userRepository,
                                     OrderRepository orderRepository) {
        return new OrderService(productRepository, userRepository, orderRepository);
    }
}
2.2.3基于注解的自动扫描:

Spring 还支持通过注解自动扫描来发现和管理 Bean。在仓库类和OrderService类上添加适当的注解,如:

// 标记为仓库类,由Spring管理
@Repository
class ProductRepositoryImpl implements ProductRepository {
    // 实现方法
}

@Repository
class UserRepositoryImpl implements UserRepository {
    // 实现方法
}

@Repository
class OrderRepositoryImpl implements OrderRepository {
    // 实现方法
}

// 标记为服务类,由Spring管理
@Service
class OrderService {
    private ProductRepository productRepository;
    private UserRepository userRepository;
    private OrderRepository orderRepository;

    // 通过构造函数注入,Spring会自动匹配类型并注入
    public OrderService(ProductRepository productRepository,
                        UserRepository userRepository,
                        OrderRepository orderRepository) {
        this.productRepository = productRepository;
        this.userRepository = userRepository;
        this.orderRepository = orderRepository;
    }

    public void placeOrder(Order order) {
        User user = userRepository.getUser(order.getUserId());
        List<Product> products = productRepository.getProducts(order.getProductIds());
        // 处理订单逻辑,如计算总价、验证库存等
        //...
        orderRepository.saveOrder(order);
    }
}

通过@Repository注解标记仓库类,@Service注解标记服务类,Spring 会自动扫描这些带有注解的类并将它们作为 Bean 进行管理。同时,在OrderService的构造函数中,Spring 会根据参数类型自动将对应的仓库类 Bean 注入进去。


3. IoC 容器的职责

3.1创建对象:

IoC 容器负责根据配置(XML、Java 配置或注解)创建对象。它会在适当的时候(例如应用程序启动时或者第一次请求某个对象时)实例化对象。对于单例模式的对象(这是 Spring 中默认的 Bean 作用域),容器只会创建一个实例,并在整个应用程序生命周期中重复使用这个实例。对于其他作用域(如原型作用域,每次请求都会创建一个新的实例)的对象,容器会按照相应的规则进行创建。

3.2生命周期管理:

除了创建对象,容器还管理对象的生命周期。它可以在对象创建时执行初始化操作,例如调用对象的初始化方法或者执行一些依赖注入的操作。在对象不再需要时,容器可以负责销毁对象,并执行一些清理资源的操作。以一个数据库连接池为例,当应用程序启动时,IoC 容器可以创建数据库连接池对象,并初始化一定数量的连接。在应用程序运行过程中,对象被反复使用,当应用程序关闭时,容器可以负责关闭连接池,释放所有的数据库连接。


二、依赖注入(DI)

1. 作为 IoC 实现方式的依赖注入

依赖注入(DI)是实现控制反转(IoC)的一种具体手段。它强调的是如何将对象所依赖的其他对象传递给这个对象。可以把 IoC 看作是一种设计理念,而 DI 是实现这种理念的具体技术。

依赖注入的核心思想是将这些被依赖者对象的创建和获取从依赖者对象中分离出来,由外部(通常是一个容器,如 Spring 的 IoC 容器)负责将被依赖者对象 “注入” 到依赖者对象中。这样,依赖者对象不需要知道被依赖者对象是如何创建的,只需要使用它提供的功能即可。


2. 为什么需要依赖注入

2.1降低耦合度

在没有依赖注入的情况下,依赖者对象会直接在自己的代码中创建被依赖者对象。
如:

class UserService {
    private UserRepository userRepository = new UserRepositoryImpl();
    // 业务方法
}

这种方式使得UserService和UserRepositoryImpl紧密耦合。如果需要更换UserRepository的实现(比如从使用关系型数据库改为使用非关系型数据库),就必须修改UserService的代码。而通过依赖注
入,UserService只依赖于UserRepository的接口,具体的实现可以在外部配置,从而降低了耦合度。


2.2提高可维护性和可测试性

从维护角度看,当系统规模变大,对象之间的依赖关系复杂时,直接在类内部创建依赖对象会使代码难以理解和修改。而依赖注入将依赖关系的配置集中在外部,使得代码结构更加清晰。

对于测试来说,依赖注入也非常重要。假设我们要对UserService进行单元测试,在没有依赖注入时,UserService直接创建了UserRepositoryImpl,这使得在测试时很难隔离UserRepository的真实行为。例如,如果UserRepositoryImpl涉及数据库访问,在单元测试中可能不希望真正访问数据库。通过依赖注入,可以轻松地将一个模拟的UserRepository(只返回测试数据,不进行真实的数据库操作)注入到UserService中,从而方便地对UserService的业务逻辑进行单元测试。


3.依赖注入的具体方式

3.1构造函数注入

构造函数注入是指通过依赖者对象的构造函数来传递被依赖者对象。当创建依赖者对象时,必须同时提供被依赖者对象作为构造函数的参数。
如:

class UserService {
    private UserRepository userRepository;
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    // 业务方法
}

在这里,UserService的构造函数接受一个UserRepository对象作为参数。当创建UserService的实例时,外部(如 IoC 容器)会将一个UserRepository对象传递进来,从而将依赖注入到UserService中。


3.2Setter 注入

Setter 注入是通过依赖者对象的 Setter 方法来传递被依赖者对象。依赖者对象先被创建,然后可以通过调用 Setter 方法来设置其依赖对象。
如:

class UserService {
    private UserRepository userRepository;
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    // 业务方法
}

在这个例子中,OrderService的构造函数明确地列出了它所依赖的三个对象。当创建OrderService的实例时,外部容器会将这三个对象注入进来,使得OrderService能够正常工作。


3.3字段注入

字段注入是直接在依赖者对象的字段上使用注解(如 Spring 中的@Autowired),让外部容器自动将被依赖者对象注入到该字段中。
如:

import org.springframework.beans.factory.annotation.Autowired;
@Service
class UserService {
    @Autowired
    private UserRepository userRepository;
    // 业务方法
}

@Autowired注解告诉 Spring 容器自动将一个UserRepository对象注入到UserService的userRepository字段中。

标签:依赖,DI,对象,反转,UserRepository,OrderService,IoC,注入
From: https://blog.csdn.net/bufangbufang/article/details/144249098

相关文章

  • 92. 反转链表 II
    链接:92.反转链表II-力扣(LeetCode)方法一:需要分类讨论/*总体思路就是:pleft指向left所在的节点pright指向right所在的节点beforeleft指向left的前一个节点,或者叫做前面没有反转部分的尾节点behindright指向right的后一个节点,或者叫做后面没有反转部分......
  • JVM优化,Redis,MySQL相关面试题
    一、平常对SQL优化的了解1.索引优化创建索引:为常用的查询字段创建索引,可以显著提高查询速度。例如,为订单金额的字段创建索引,可以加速按订单金额的排序操作。优化索引:定期维护索引,避免索引碎片化,保持索引性能。使用覆盖索引:通过创建覆盖索引,可以避免回表操作,进一步提高查......
  • 【C++】关于 Visual Studio 的使用技巧(保姆级教程)
    目录fliter视图输出文件位置设置查看预处理结果将目标文件转换为可读的汇编自定义程序入口调试时查看变量在内存中的具体值查看代码的反汇编fliter视图visualstudio默认是filter视图(中文为筛选器)项目下的是filter而非硬盘目录里实际的文件夹,这时新建的也是filter想要查看......
  • python学习-condition
    条件判断1.三个关键词:ifelseelif(即为elseif)(1)if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的elif和else(2)当if后面的条件语句不满足时,与之相对应的else中的代码块将被执行。ifa==1:print('right')else:print('wro......
  • Redis指南【5】图解深入 RDB 与 AOF
    前言Redis它是一个键值对的内存数据库,读写数据都是基于内存的,所以它的性能非常高,但同时如果服务器一旦宕机,那么内存的数据是不可恢复的,所以,redis想到了持久化,如何把内存中的数据优雅的同步到磁盘中,以便redis在重启时能够恢复原有的数据,这就是持久化。Redis的持久化有三......
  • Task05 :conditionals
    条件在Python中,条件语句用于根据不同的条件执行不同的代码块。常见的条件语句有if语句、if-else语句和if-elif-else语句。例如:a=int(input('请输入一个数字:'))#由于input()函数输入的数据默认为字符串,所以使用int()转换为整数ifa>=10:print(a)#如果a>=10......
  • 第58篇 Redis常用命令
    1.基本操作2.字符串(Strings)3.列表()4.哈希(Hashes)5.位图(Bitmaps)6.位域(Bitfields)7.集合(Sets)8.有序集合(SortedSets)9.流(Streams)10。地理空间(Geospatial)11.HyperLogLog......
  • Confusion pg walkthrough Intermediate
    namp┌──(root㉿kali)-[~]└─#nmap-p--A192.168.188.99StartingNmap7.94SVN(https://nmap.org)at2024-12-0404:50UTCNmapscanreportfor192.168.188.99Hostisup(0.072slatency).Notshown:65532closedtcpports(reset)PORTSTATESERVICE......
  • 【笔记软件】Obsidian的使用
    背景之前使用的是语雀和有道云笔记。语雀,偏向知识库的使用。有道云笔记,偏向随笔的使用。但是这2个都是联网版的笔记,断网或服务器异常都会影响使用,尤其语雀。而且部分知识库和随笔会存在私密内容,也不方便放在云端。随后尝试了有些离线或本地优先的笔记软件,印象笔记、wolai、幕布......
  • Redis——个人笔记留存
    今日内容1.redis1.概念2.下载安装3.命令操作1.数据结构4.持久化操作5.使用Java客户端操作redisRedis1.概念:redis是一款高性能的NOSQL系列的非关系型数据库1.1.什么是NOSQLNoSQL(NoSQL=NotOnlySQL),意即“不仅......