大白话理解IoC和DI

引言

Spring是Java领域最受欢迎的开发框架之一,其核心功能之一就是Spring容器,也就是IoC容器。这篇文章,我们就来聊聊Spring的两大核心功能,控制反转(IOC)和依赖注入(DI)。

文章思路是这样:

  1. 传统开发存在哪些问题
  2. 为了解决这些问题引入IoC 和DI
  3. 总结

传统开发存在的问题

我最早看到IoCDI这两个名词的时候,我脑子里是懵的,理解不了。既然陌生的东西理解不了,我们就看看它为什么出现?它的出现是为了解决什么问题。

我们先假设有三个类,分别为类A、B和C, 其中,main 函数调用了类A, 类A 依赖了类B和类C,类B依赖类C,如果用传统模式开发,大概是下图中的情况,那么传统的这种开发模式存在什么问题呢?看下图,不难发现对于类C,我们在类A 和类B 中都进行了实例化,也就是new了一个C对象 那么:
第一个问题:代码重复,且浪费内存资源。
第二个问题:耦合性太高了,类C是以硬编码的方式创建出来的,如果类C 发生了重大变化,都会直接影响类A和B,
第三个问题:难以测试。在测试过程中,很难将被测试对象与其依赖的对象解耦,从而无法独立地测试被测试对象的逻辑。
第N个问题:其他的就不多说了
在这里插入图片描述

我们有没有什么方式能解决上面这些问题呢? 有,就是IoC和DI。

接下来我们就分析一下IoCDI是怎样解决这些问题的:

什么是IOC?

IoC( Inversion of Control ) 注意哦, 它是一个技术思想,不是一个技术实现。它描述的是 Java开发领域对象的创建,管理问题,我们看了上面的图就知道在传统开发中,存在依赖时,往往都会new一个依赖的对象,那么在IoC 思想下,就不用去new 对象了。而是由IoC容器去帮我们实例化对象并且管理它。

为什么叫控制反转呢?

1.控制了什么?
控制了对象创建、管理的权力

2.反转了什么?
将控制权交给了外部(IoC容器)

如下图:对于对象的创建和管理都交给了IoC容器,当需要使用的时候,不需要去new了,直接去IoC容器中拿。

在这里插入图片描述

什么是DI

DI:Dependancy Injection (依赖注入)

其实DI和IoC是对同一件事情的不同描述,IoC是一种设计原则,是一种思想,而DI是IoC的一种具体实现,前面我们提到,对象统一交给IoC创建并管理,在依赖的地方不需要去new, 这儿就可以理解依赖注入了:

再看看上面的图,类A 依赖类B和C, 注意看图中的伪代码,类A依赖类B和C,那么只需要在类A中声明要依赖的对象,那么通过构造函数注入或者属性注入或者方法注入的方式,将依赖的对象注入到对象中。

IoC是如何解决难以测试问题的呢?

假设我们有一个简单的应用程序,其中有一个服务类 UserService,它依赖于一个数据访问对象 UserDAO 来获取用户信息。我们想要测试 UserService 中的 getUserById 方法,以确保它能够正确地返回指定用户的信息。

首先,我们来看一下没有使用依赖注入的情况:

public class UserService {
    private UserDAO userDAO;

    public UserService() {
        this.userDAO = new UserDAO(); // 在构造函数中直接创建依赖对象
    }

    public User getUserById(int userId) {
        return userDAO.getUserById(userId);
    }
}

public class UserDAOTest {
    @Test
    public void testGetUserById() {
        UserService userService = new UserService(); // 创建被测试对象
        User user = userService.getUserById(1); // 调用方法
        // 断言用户信息是否正确
        assertEquals("husu", user.getName());
        assertEquals("ricardoyhu@163.com", user.getEmail());
    }
}

在上面的代码中,UserService 在构造函数中直接创建了 UserDAO 对象,这样在测试的时候就无法替换掉实际的 UserDAO 对象,导致测试无法独立进行,也无法模拟 UserDAO 的行为。

现在,让我们使用依赖注入来改进代码:

public class UserService {
    @Autowired
    private UserDAO userDAO;

    public User getUserById(int userId) {
        return userDAO.getUserById(userId);
    }
}

public class UserDAOTest {
    @Test
    public void testGetUserById() {
        // 创建模拟的UserDAO对象
        UserDAO mockUserDAO = Mockito.mock(UserDAO.class);
        // 设置模拟对象的行为
        when(mockUserDAO.getUserById(1)).thenReturn(new User(1, "husu", "ricardoyhu@163.com"));
        
        UserService userService = new UserService(mockUserDAO); // 通过构造函数注入模拟对象
        User user = userService.getUserById(1); // 调用方法
        // 断言用户信息是否正确
        assertEquals("husu", user.getName());
        assertEquals("ricardoyhu@163.com", user.getEmail());
    }
}

在上面的代码中,我们@Autowired将 UserDAO 对象注入到了 UserService 中,这样在测试时就可以使用模拟的 UserDAO 对象来替代实际的 UserDAO 对象。我们使用了 Mockito 框架来创建模拟对象,并设置了模拟对象的行为,以模拟 UserDAO 的返回结果。这样一来,我们就可以独立地测试 UserService 中的 getUserById 方法,而不用担心 UserDAO 的实际行为或状态,从而使得测试更加容易进行。

IoC是如何解决代码重复、性能提升的呢?

如何解决代码重复,其实上面已经说到了,就是又IoC容器创建、管理对象,不用到处new了,
说到性能提升就不得不提到IoC容器的生命周期。

IoC容器的生命周期如以下三个阶段

  1. 初始化阶段 : IoC容器在启动时会进行初始化,包括加载配置文件、解析注解、扫描类路径等操作,在这个阶段,IoC容器会创建并管理所有的Bean定义,并根据配置文件或者注解来实例化和装配Bean。
  2. 使用阶段:IoC容器初始化完成后,应用程序可以通过IoC容器来获取所需的Bean对象,并且利用这些对象来完成各种业务逻辑,这个阶段,IoC容器负责管理对象的生命周期,包括对象的创建、依赖注入、初始化等操作。
  3. 销毁阶段:当应用程序关闭时,IoC容器会进行销毁操作,释放资源并销毁所有的Bean对象,在这个阶段,如果Bean类中定义了特定的销毁方法,IOC容器会调用这些方法。如果没有定义销毁方法,IOC容器就不会执行任何额外的销毁操作,而是简单地释放Bean对象所占用的资源,如数据库连接、文件句柄之类的,这个阶段的执行顺序与初始化阶段相关,即先销毁依赖关系较少的Bean,再销毁依赖关系较多的Bean,以保证销毁的顺序正确。

一个Bean 在容器启动时被创建,就会一直存在于容器中,直到应用程序关闭时被销毁,这种管理方式保证了对象的单例性和全局可访问性,也因此提高了系统的性能和效率。

意思就是不会重复创建,不会浪费资源。少了多余的创建和销毁的性能开销,自然就提高系统性能啦。

总结

通过深入理解IOC与DI的核心概念和实践应用,我们可以更好地掌握Spring框架的原理和功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/587662.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Go 语言基础(二)【数组、切片、指针、map、struct】

1、数组 特别需要注意的是:在 Go 语言中,数组长度也是数组类型的一部分!所以尽管元素类型相同但是长度不同的两个数组,它们的类型并不相同。 1.1、数组的初始化 1.1.1、通过初始化列表{}来设置值 var arr [3]int // int类型的数…

09_Scala函数和对象

文章目录 函数和对象1.函数也是对象 scala中声明了一个函数 等价于声明一个函数对象2.将函数当作对象来用,也就是访问函数,但是不执行函数结果3.对象拥有数据类型(函数类型),对象可以进行赋值操作4.函数对象类型的省略写法,也就是…

SCI一区 | MFO-CNN-LSTM-Mutilhead-Attention多变量时间序列预测(Matlab)

SCI一区 | MFO-CNN-LSTM-Mutilhead-Attention多变量时间序列预测(Matlab) 目录 SCI一区 | MFO-CNN-LSTM-Mutilhead-Attention多变量时间序列预测(Matlab)预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现MFO-CNN…

常见公式的几何解释

本文旨在深入探讨常见数学公式的几何意义,通过直观的图形和解释,帮助读者更好地理解并掌握这些公式的本质。文章首先概述了公式与几何图形之间的紧密联系,然后选取了几个典型的数学公式,进行详细解析。每个公式都将配以相应的几何…

vuex的学习

首先下载vuex,然后建立一个目录在vueX中 接着在index。js文件夹中引入 引入后导出这个文件 在main.js文件中导入,这样vue就有了状态管理 接着我创建了2个组件,在 里边规定了一个num:0 在 打印出来就可以看见 映射函数mapState,必…

数据结构算法——链表带环问题——数学深度解析

前言:本节内容主要是讲解链表的两个问题 :1、判断链表是否带环; 2、一个链表有环, 找到环的入口点。 本节内容适合正在学习链表或者链表基础薄弱的友友们哦。 我们先将问题抛出来,友友们可以自己去力扣或者牛客网去找相应题目&…

基于SSM的个人博客系统(四)

目录 5.3 博客类别管理模块 5.3.1 添加博客类别 5.3.2 修改博客类别 5.3.3 删除博客类别 5.3.4 显示博客类别 5.4 评论管理模块 5.4.1 审核评论 5.4.2 删除评论 前面内容请移步 基于SSM的个人博客系统(三) 个人博客系统的设计与实现免费源码…

头歌:Spark GraphX—寻找社交媒体中的“影响力用户”

第1关:认识Pregel API 简介 Spark GraphX中提供了方便开发者的基于谷歌Pregel API的迭代算法,因此可以用Pregel的计算框架来处理Spark上的图数据。GraphX的Pregel API提供了一个简明的函数式算法设计,用它可以在图中方便的迭代计算,如最短路径、关键路径、n度关系等,也可以…

【C++】STL学习之优先级队列

🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《C》 《Linux》 ❤️感谢大家点赞👍收藏⭐评论✍️ 文章目录 前言一、优先级队列的使用1.1 基本功能1.2 优先级模式切换1.3 相关题目 二、模拟实现优先级…

AI赋能不应贵气:深度解读AI助力企业渡过经济寒冬以及如何落地AI的路径

AI很棒可是给人感觉“很贵”因此我不敢用 继GPT4后Dalle3、Sora、GPT4.5、GPT5的消息以及前天突然出现的GPT 2.0(GPT二代,有人说这就是OPEN AI的新产品:Q*)但凡涉及到AI的一系列新闻给人予很震撼的感觉。放眼望去AI正在欣欣向荣。…

洛谷 P5854:【模板】笛卡尔树

【题目来源】https://www.luogu.com.cn/problem/P5854【题目描述】 给定一个 1∼n 的排列 p,构建其笛卡尔树。 即构建一棵二叉树,满足: 1.每个节点的编号满足二叉搜索树的性质。← 优先级 pri 满足二叉搜索树(BST)的性…

强化学习(Reinforcement learning)基本概念

概念: 强化学习是在与环境互动中为达到一个目标而进行的学习过程 三层结构: 基本元素:agent、environment、goal agent:可以理解为玩家,即某个游戏的参与方 environment:环境本身,可以理…

Web后端开发中对三层架构解耦之控制反转与依赖注入

内聚与耦合 内聚 比如说我们刚刚书写的员工的实现类 在这里我们仅仅书写的是和员工相关的代码 而与员工无关的代码都没有放到这里 说明内聚程度较高 耦合 以后软件开发要高内聚 低耦合 提高程序灵活性 扩拓展性 分析代码 如何解耦 创建容器 提供一个容器 存储东西 存储E…

基于FPGA的数字信号处理(5)--Signed的本质和作用

前言 Verilog中的signed是一个很多人用不好,或者说不太愿意用的一个语法。因为不熟悉它的机制,所以经常会导致运算结果莫名奇妙地出错。其实了解了signed以后,很多时候用起来还是挺方便的。 signed的使用方法主要有两种,其中一种…

Android View事件分发面试问题及回答

问题 1: 请简述Android中View的事件分发机制是如何工作的? 答案: 在Android中,事件分发机制主要涉及到三个主要方法:dispatchTouchEvent(), onInterceptTouchEvent(), 和 onTouchEvent(). 当一个触摸事件发生时,首先被Activity的…

配置 Trunk,实现相同VLAN的跨交换机通信

1.实验环境 公司的员工人数已达到 100 人,其网络设备如图所示。现在的网络环境导致广播较多网速慢,并且也不安全。公司希望按照部门划分网络,并且能够保证一定的网络安全性。 其网络规划如下。 PC1和 PC3为财务部,属于VLAN 2&…

邦注科技 温控箱对企业的重要性

注塑加工是将加热的熔融塑料注入模具中形成所需产品的工艺过程。良好的注塑加工工艺需要控制好许多参数,其中最重要的因素之一就是模具的温度。模具温度的不稳定会导致产品尺寸大小、表面缺陷等方面的问题,甚至会导致生产不良品,加大生产成本…

Educational Codeforces Round 165 (Rated for Div. 2 ABCDE 题)视频讲解

A. Two Friends Problem Statement Monocarp wants to throw a party. He has n n n friends, and he wants to have at least 2 2 2 of them at his party. The i i i-th friend’s best friend is p i p_i pi​. All p i p_i pi​ are distinct, and for every i ∈…

通义灵码实战系列:一个新项目如何快速启动,如何维护遗留系统代码库?

作者:别象 进入 2024 年,AI 热度持续上升,翻阅科技区的文章,AI 可谓是军书十二卷,卷卷有爷名。而麦肯锡最近的研究报告显示,软件工程是 AI 影响最大的领域之一,AI 已经成为了软件工程的必选项&…

FLUKE万用表17B+的电压档最大内阻

项目中遇到一个测量兆欧级别电阻两端电压的问题,发现按照上图中的电路搭建出来的电路测得的电压为8.25V左右,按理说应为9V才对,后来想到万用表测量电压档不同的档位会有不同内阻,测量的电阻应远小于万用表电压档内阻才有效。本次测…