什么是依赖注入和控制反转在Spring框架中?

146
"依赖注入"和"控制反转"通常被视为使用Spring框架开发Web框架的主要优势。以简单易懂的方式解释一下它是什么,如果可能的话,请举一个例子。

可能是什么是控制反转?的重复问题。 - Steve Chambers
4
@SteveChambers,这不是重复的问题,这个问题被问在Spring框架的视角下。那个问题是以一般的视角提出的。 - VedantK
https://dev59.com/LrXna4cB1Zd3GeqPM5DX#57387256 - nitinsridar
12个回答

302
  • 依赖注入使应用程序松耦合。
  • 在Spring中,对象定义它们的关联(依赖项),并且不必担心如何获取这些依赖项。提供创建对象所需的所需依赖是Spring的责任。

例如:假设我们有一个Employee对象,并且它依赖于Address对象。我们将定义一个与Employee相对应的bean,以定义其对Address对象的依赖关系。

当Spring尝试创建Employee对象时,它将看到Employee依赖于Address,因此它将首先创建Address对象(依赖对象),然后将其注入到Employee对象中。

  • 控制反转(IoC)和依赖注入(DI)可互换使用。通过DI实现IoC,DI是提供依赖项的过程,而IoC是DI的最终结果。(注意: DI不是实现IoC的唯一方式,还有其他方法。)

  • 通过DI,创建对象的责任从我们的应用程序代码转移到Spring容器;这种现象称为IoC。

  • 可以通过setter注入或构造函数注入实现依赖注入。

4
我不同意。我认为这并不是一个清晰的解释。为什么不能在“Employee”内部实例化“Address”,而要获取框架来创建它并注入它?需要举一个稍微详细一些的例子。 - Boris
3
没人说你不能实例化自己的对象。但这个答案的唯一目的是展示如何通过依赖注入来实现相同的效果。你可以同时拥有依赖注入和由客户端代码实例化的对象。这仍然被称为控制反转(IOC),至少部分地是这样。 - bogdan.rusu
https://dev59.com/LrXna4cB1Zd3GeqPM5DX#57387256 - nitinsridar

38

我将写下我对这两个术语的简单理解:(为了快速理解,只需阅读示例)

  • 依赖注入(DI):
    依赖注入通常意味着将一个依赖对象作为参数传递给一个方法,而不是让该方法创建依赖对象
    实际上,这意味着该方法没有直接依赖于特定的实现;满足要求的任何实现都可以作为参数传递。

    通过这种实现对象定义它们的依赖关系。Spring 使其可用。这导致了松耦合的应用程序开发。

    快速示例:当创建 EMPLOYEE 对象时,如果 ADDRESS 对象被定义为 EMPLOYEE 对象的依赖项,则会自动创建 ADDRESS 对象。

  • 控制反转(IoC)容器:
    这是框架的共同特性,IoC 管理 Java 对象
    - 从实例化到销毁,都通过其 BeanFactory 进行管理。
    - 被 IoC 容器实例化的 Java 组件称为 bean,并且 IoC 容器管理 bean 的范围、生命周期事件和任何已被配置和编码的 AOP 特性

    快速示例:控制反转是关于获得自由、更灵活和更少依赖的。当您使用台式计算机时,您是被奴役的(或者说,受到控制)。您必须坐在屏幕前并看着它。使用键盘输入和鼠标导航。糟糕编写的软件甚至可能让您更受控制。如果您用笔记本电脑替换台式计算机,则可以在某种程度上反转控制。您可以轻松携带它并移动它。因此,现在您可以控制您与计算机的位置,而不是计算机控制它.

    通过实现控制反转,软件/对象消费者可以获得更多对软件/对象的控制/选项,而不是被控制或拥有较少的选项。

    控制反转作为设计准则具有以下目的:
    - 执行某个任务的执行与实现解耦。
    - 每个模块都可以专注于其设计目的。
    - 模块不对其他系统做出任何假设,而是依赖于它们的合同。
    - 替换模块对其他模块没有副作用。

我会在这里保持抽象,您可以访问以下链接以更详细地了解该主题。

一个带有示例的好读物

详细解释


16

控制反转 - 意味着将创建和实例化Spring Bean的控制权交给Spring IOC容器,开发人员只需要在Spring XML文件中配置Bean。

依赖注入 -

考虑一个类Employee

class Employee { 
   private int id;
   private String name;
   private Address address;

   Employee() {
     id = 10;
     name="name";
     address = new Address();
   }


}

考虑 Address 类

class Address {
   private String street;
   private String city;

   Address() {
     street="test";
     city="test1";

  }
}

在上述代码中,地址类的值仅在实例化Employee类时设置,这是Address类对Employee类的依赖。Spring使用依赖注入概念解决了这个问题,提供了两种注入依赖的方式。

  1. Setter注入

在Employee类中设置一个接收Address类引用的Setter方法。

public void setAddress(Address addr) {
    this.address = addr;
}
  1. 构造函数注入

Employee类中的构造函数接受Address参数。

Employee(Address addr) {
      this.address = addr;
}

通过使用setter/constructor注入,可以独立设置Address类的值。


14

在Spring中,对象是松耦合的,即每个类都是独立的,因此一切都可以单独进行测试。但是,在使用这些类时,一个类可能依赖其他需要先实例化的类。

因此,我们告诉Spring类A依赖于类B。因此,在为类A创建bean(像类一样)时,它先实例化类B,然后使用setter或constructor DI方法将其注入类A。也就是说,我们在运行时告诉了Spring依赖关系。这就是DI。

由于我们将创建对象(bean)、维护它们及其聚合的责任交给了Spring而不是硬编码,因此我们称其为控制反转(IOC)。


9

控制反转(IoC):

IoC是一种设计模式,它描述了在系统中反转控制流,使得执行流程不受中央代码的控制。这意味着组件只应该依赖于其他组件的抽象,并且不负责处理依赖对象的创建。而是通过依赖注入(DI),由IoC容器在运行时提供对象实例。

IoC可以促进更好的软件设计,以实现组件的重用、松散耦合和易于测试。

依赖注入(DI):

DI是一种将依赖项传递到对象构造函数的技术。如果对象已从容器加载,则其依赖项将自动由容器提供。这允许您消费依赖项,而无需手动创建实例。这减少了耦合并使您对对象实例的生命周期具有更大的控制。

点击查看更多


8

Spring: Spring是Java平台上的“控制反转”容器。

控制反转(IoC):控制反转(IoC)是面向对象编程的一种实践,通过一个“组装器”对象在运行时绑定对象耦合,在编译时通常无法通过静态分析知道这些信息。

依赖注入(DI):“依赖注入是一种软件设计模式,可以消除硬编码的依赖关系,并使其能够在运行时或编译时更改。”-维基百科。


1
这有什么比已经存在的更简单的吗(这是答案的来源)?除非术语周围的双引号神奇地使事情变得更简单,否则它并没有考虑到OP对简单性的要求。 - Flame of udun

8

简单来说..

  • 控制反转(IOC)是一种概念,意味着:不要使用new操作符创建对象,让容器为您创建。
  • 依赖注入(DI)是通过以下Spring的方式注入框架组件的一种方式:
  1. 构造函数注入
  2. Setter/Getter注入
  3. 字段注入

6

控制反转是软件架构的一种通用设计原则,有助于创建易于维护的可重用、模块化软件框架。

它是一种设计原则,在其中流程控制“接收”自通用编写的库或可重用代码。

为了更好地理解它,让我们看看我们在编码的早期如何编码。在过程/传统语言中,业务逻辑通常控制应用程序的流程并“调用”通用或可重用的代码/函数。例如,在简单的控制台应用程序中,我的控制流由我的程序指令控制,这可能包括对一些通用可重用函数的调用。

print ("Please enter your name:");
scan (&name);
print ("Please enter your DOB:");
scan (&dob);

//More print and scan statements
<Do Something Interesting>

//Call a Library function to find the age (common code)
print Age

相比之下,使用控制反转(IoC),框架是可重用的代码,用于“调用”业务逻辑。
例如,在基于Windows的系统中,将已经有一个框架可用于创建UI元素,如按钮、菜单、窗口和对话框。当我编写应用程序的业务逻辑时,框架的事件将调用我的业务逻辑代码(当事件被触发时),而不是相反。
虽然框架的代码不知道我的业务逻辑,但它仍然知道如何调用我的代码。这是通过事件/委托、回调等实现的。在这里,流程的控制是“倒置”的。
因此,与其依赖于静态绑定对象的流程,流程取决于整个对象图和不同对象之间的关系。
依赖注入是一种实现对象依赖关系解析的IoC原则的设计模式。
简单来说,当您尝试编写代码时,您将创建和使用不同的类。一个类(Class A)可能使用其他类(Class B和/或D)。因此,Class B和D是Class A的依赖项。
一个简单的类比可以是汽车类。汽车可能依赖于其他类,如发动机、轮胎等。
依赖注入建议,应该将具体的依赖实例注入到类中,而不是依赖的类(例如汽车类)创建其自己的依赖项(例如发动机和轮胎)。
让我们以一个更实际的例子来理解。假设您正在编写自己的文本编辑器。除其他事项外,您可以拥有一个拼写检查器,为用户提供检查其文本中的拼写错误的功能。这样的代码的简单实现可以是:
Class TextEditor
{

    //Lot of rocket science to create the Editor goes here

    EnglishSpellChecker objSpellCheck;
    String text;

    public void TextEditor()

    {   

        objSpellCheck = new EnglishSpellChecker();

    }

    public ArrayList <typos> CheckSpellings()
    {

        //return Typos;

    }

}

乍一看,一切都很顺利。用户会写一些文本。开发人员将捕获这些文本并调用CheckSpellings函数,然后找到一组错别字,并将其展示给用户。
一切看起来都很好,直到有一天,有个用户开始在编辑器中写法语。
为了支持更多语言,我们需要更多的拼写检查器。可能是法语、德语、西班牙语等。
在这里,我们创建了一个紧密耦合的代码,其中“English”SpellChecker与我们的TextEditor类紧密耦合,这意味着我们的TextEditor类依赖于EnglishSpellChecker或者说EnglishSpellCheker是TextEditor的依赖项。我们需要消除这种依赖关系。此外,我们的Text Editor需要一种方式来在运行时根据开发人员的判断来保留任何拼写检查器的具体引用。
所以,正如我们在DI的介绍中看到的那样,它建议该类应该被注入它的依赖项。因此,注入所有依赖项到被调用的类/代码应该是调用代码的责任。因此,我们可以重构我们的代码如下:
interface ISpellChecker
{

    Arraylist<typos> CheckSpelling(string Text);

}

Class EnglishSpellChecker : ISpellChecker

{

    public override Arraylist<typos> CheckSpelling(string Text)

    {

        //All Magic goes here.

    }

}



Class FrenchSpellChecker : ISpellChecker

{

    public override Arraylist<typos> CheckSpelling(string Text)

    {

        //All Magic goes here.

    }

}

在我们的例子中,TextEditor类应该接收ISpellChecker类型的具体实例。
现在,依赖关系可以通过构造函数、公共属性或方法进行注入。
让我们尝试使用构造函数DI来改变我们的类。修改后的TextEditor类将看起来像这样:
Class TextEditor

{

    ISpellChecker objSpellChecker;

    string Text;



    public void TextEditor(ISpellChecker objSC)

    {

        objSpellChecker = objSC;

    }



    public ArrayList <typos> CheckSpellings()

    {

        return objSpellChecker.CheckSpelling();

    }

}

这样,在创建文本编辑器时,调用代码可以向TextEditor实例注入适当的SpellChecker类型。

您可以在这里阅读完整的文章。


4

IOC是一种技术,你可以让别人为你创建对象。在Spring的情况下,这个“别人”就是IOC容器。

依赖注入是一种技术,其中一个对象提供另一个对象的依赖项。


3

IOC代表控制反转,是一个更高级别的概念,它表示我们将对象的创建控制从调用者转移到被调用者。

没有控制反转,你需要负责对象的创建。在控制反转情况下,框架负责创建类的实例。

依赖注入是实现控制反转的方法。为了将控制权留给框架或任务,我们声明依赖关系,IOC容器将这些依赖项注入到我们的类中(即框架为我们创建一个实例并提供给我们的类)。

那么这有什么优势呢?

首先,Spring会管理类及其生命周期。Spring完全管理从创建到销毁的过程。

其次,类之间的耦合度降低。一个类不与另一个类的实现紧密耦合。如果实现发生变化,或者您想要更改注入接口的实现,可以轻松地在代码库中不需要手动更改所有实例。

第三,类之间的内聚性增强。高内聚意味着将相关的类一起保持在一起。因为我们在其他类中注入接口,所以清楚哪些类对于调用类的操作是必要的。

第四,可测试性增强。因为我们在构造函数中使用接口,所以可以轻松地将实现与模拟实现交换。

第五,使用JDK动态代理代理对象。JDK动态代理需要使用接口,这是正确的,因为我们正在注入这些接口。然后可以将此代理用于Spring AOP、事务处理、Spring数据、Spring安全等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接