Spring5源码分析(004)——IoC篇之理解IoC

注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总 


  本文主要记录笔者对 IoC 的一些理解和以及相关参考。目录结构如下:

 

1、对 IoC 和 DI 的理解

1.1、IoC 是什么

  用过 Spring 框架的开发人员,基本上都会接触过 IoC 这个比较核心的概念。而项目中只要用到 Spring 框架的,无论是直接或者是间接,基本上都会用到 IoC 容器这个功能。下面笔者简要谈一谈 IoC。

  IoC ,全称 Inversion of Control,也就是常说的”控制反转“。与 IoC 一同提起的还有另外一个概念:DI(Dependency Injection,依赖注入)。

  那 IoC 该如何理解呢?就像 Martin Fowler 大师在《Inversion of Control Containers and the Dependency Injection pattern》提出的灵魂拷问一样:The question is: "what aspect of control are they inverting?"(哪方面的控制被反转了?)用另外一种方式来说就是:谁对谁的什么控制(哪方面的控制)被谁给反转了?这个就是 【第二章】 IoC 之 2.1 IoC基础 ——跟我学Spring3 中提到的“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”。笔者对此理一理自己的总结:

  • 谁控制谁,控制什么:对象对其依赖(对象、配置等等)的控制,控制其主动创建或者主动获取的方式,例如主动通过 new 的方式获取依赖对象的实例,主动通过工厂方法获取实例,主动进行配置的设置等,这是一种强关系,强依赖,需要知道具体的依赖、具体的获取方式;而 IoC 中,则是 IoC 容器对各种资源(例如各种 bean 、配置等)的控制,对象所有相关的资源依赖(依赖对象、配置、文件等等)的主动获取过程都被接管了,也就是控制了对象的依赖资源的获取方式。
  • 为何是反转,哪些方面反转了:对象原来对依赖都是主动创建/主动获取就是正转,而现在这些被 IoC 容器接管了,依赖的主动获取被反转给容器了,对象只要声明需要用到的依赖,怎么创建和管理就交由 IoC 容器了。

  接下来,我们再来看下 Spring 官网 docs Spring Core Technologies 中对 IoC 的一段说明:


  This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) principle. IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.


  本章介绍了 Inversion of Control(IoC,控制反转)原则的 Spring 框架实现。IoC 也被称为依赖注入(DI),这是一个对象仅可通过构造函数参数、工厂方法的参数、或者在对象实例构造时被设置或从工厂方法返回的属性来定义其依赖项的过程。之后容器在创建 bean 时注入这些依赖项。这个过程基本上就是对 bean 本身通过直接使用类的构造器或者使用类似 Service Locator模式的 机制从而控制依赖项的实例化和定位的过程进行了反转(因此才被称为控制反转)。


  理一理,Spring 框架中提到的 IoC 是指类的依赖对象的主动实例化从类本身脱离,交给了框架去处理,这里就是通过 IoC 容器进行实例化并提供,也就是说控制依赖项的获取从类本身反转到了框架的 IoC 容器了,而不是原先的通过 new 直接创建或者通过其他的方式主动获取。Spring 通过依赖注入的方式实现了这种控制。从这点来说,DI 其实是 IoC 的一种实现形式,Spring 的 IoC 容器本质上就是 DI 容器,这种说法比较具体、更具针对性。当然,它算是 IoC 的一种具体实现,声称 IoC 容器也是可以的。另外,spring 的 IoC 容器还包含了对各种资源(包括配置等)的管理控制,所以称为 IoC 容器也算是名副其实,

 

1.2、为什么需要 IoC,IoC 可以解决什么问题

  为什么需要 IoC ?可不可以不使用?

  如果单纯从从用的角度看,当然可以不使用,开发人员大可以按照以前的方式进行依赖的直接获取和管理。那为什么还要用 IoC ?笔者从另外一个角度来进行说明:IoC 可以解决什么问题,带来什么好处?

  根据上面的描述和列出的参考,笔者也觉得,从广义上讲,IoC 是一种设计思想。IoC 是软件设计中对依赖资源管理的一种顶层设计指导思想。这里的资源,可以是常见的自定义 Bean(包括其内部依赖的 bean ),也可以是各种资源(xml、propertites、网络资源、文件等),甚至是数据库连接,还可以是对流程的一种管控,只要是软件可以进行管理的,都可以当作是一种资源进行统一的控制,而不是由各个对象直接去控制获取,形成强依赖、高度耦合的关系

  IoC 对依赖资源的管理给出了一种基于抽象(接口)设计、松耦合、可扩展的指导思想。对于使用方,只需要对依赖进行描述声明即可,比如常见的服务接口,特别是其他项目的服务接口,使用方其实并不关心这依赖接口怎么创建、怎么实现的,它只关心用到的时候给出正常的反馈就行,也即是调用结果正常就行,至于依赖的接口具体怎么来的、怎么创建的、什么时候回收等等,通通由 IoC 容器集中管控处理。

  由于只有面向抽象(接口)的依赖声明,而不会涉及依赖的直接创建和获取,对象便从复杂的依赖关系中解耦,只需关注本身的具体功能设计;而同样针对相同的依赖声明,IoC 框架注入的都是一样的,依赖的具体实现一改动,都会被 IoC 框架重新注入到使用到的地方,一次修改,全部知晓。也就是把修改后的具体实现告诉 IoC 容器,和原来的声明一样,容器便会去更新所有相关引用。就如同“好莱坞原则”: don't call us , we'll call you. IoC 容器接管各种资源,并对需要用到的声明依赖进行注入。如果换成是原来直接获取的方式,那么当具体依赖改动时,开发人员往往需要找到所有的引用,然后一一进行修改,这样高度耦合的设计,往往很难进行维护和扩展,特别是对于经常需要变化的需求,更是苦不堪言。例如,对于大型复杂软件系统,很多处理复杂业务功能的业务接口,内部可能都会依赖很多细粒度服务接口,而且很多接口都会被多处依赖,例如:

public class ComplexServiceA {
    private ServiceA serviceA;
    private ServiceB serviceB;
    private ServiceC serviceC;
    private ServiceD serviceD;
    // 可能还有更多需要依赖的 Service ...
    
    // 通过构造器进行直接创建
    public ComplexServiceA() {
        serviceA = new ServiceAImpl();
        serviceB = new ServiceBImpl();
        serviceC = new ServiceCImpl();
        serviceD = new ServiceDImpl();
        // ...
    }    
}

public class ComplexServiceB {
    private ServiceA serviceA;
    private ServiceB serviceB;
    private ServiceD serviceD;
    // 可能还有更多需要依赖的 Service ...
    
    // 通过构造器进行直接创建
    public ComplexServiceB() {
        serviceA = new ServiceAImpl();
        serviceB = new ServiceBImpl();
        serviceD = new ServiceDImpl();
        // ...
    }
}

  当 ServiceAImpl 需要修改,或者是修改名称、甚至是废弃了,换成新增的 ServiceAProImpl等等,所有用到 ServiceAImpl 的都需要进行修改,很难想象当有更多的类(对象)甚至是项目需要依赖到 ServiceAImpl 时,这种改动量有多大。这个时候就可以体会到 IoC 的好处了,优化后可能就是一直这个样子了,ServiceA 具体实现是怎么改动都不需要关心了,只要原来的约定还存在,开发人员就可以一直这样用着。

public class ComplexServiceA {
    private ServiceA serviceA;
    private ServiceB serviceB;
    private ServiceC serviceC;
    private ServiceD serviceD;
    // 可能还有更多需要依赖的 Service ...

    // setter
    public void setServiceA(ServiceA serviceA ) {
        this.serviceA = serviceA;
    }
    public void setServiceB(ServiceB serviceB ) {
        this.serviceB = serviceB;
    }
    public void setServiceC(ServiceC serviceC ) {
        this.serviceC = serviceC;
    }
    public void setServiceD(ServiceD serviceD ) {
        this.serviceD = serviceD;
    }
}

public class ComplexServiceB {
    private ServiceA serviceA;
    private ServiceB serviceB;
    private ServiceD serviceD;
    // 可能还有更多需要依赖的 Service ...
    
    // setter
    public void setServiceA(ServiceA serviceA ) {
    this.serviceA = serviceA;
    }
    public void setServiceB(ServiceB serviceB ) {
        this.serviceB = serviceB;
    }
    public void setServiceD(ServiceD serviceD ) {
        this.serviceD = serviceD;
    }
}

  总结下,笔者认为使用 IoC 可以带来如下好处:

  • 面向抽象(接口)设计:只需要声明依赖,并提供注入的方式,依赖抽象而不涉及到具体实现,只要声明的接口约定不改动,使用方基本上无需任何改动。
  • 松耦合:只需要声明依赖,并提供注入的方式,不涉及具体的创建获取过程,IoC 容器负责创建和维护。
  • 易扩展和复用:业务具体实现可随时变更扩展,基本不涉及使用方;由于使用时只需要进行依赖声明,IoC 容器会自动注入,复用起来就更加方便了。

  由此,其实可以大致看出,Spring 的 IoC 容器,不出意外的话,其实就是一个对各种资源的管理容器,例如各种 bean 对象的生命周期(创建、回收等)以及对象之间的依赖关系管理(注入依赖),以及各种 resources (尤其是各种 properties/xml 等)的管理等。当然,这也是马后炮而已。

 

1.3、IoC 和 DI

  上面讲完了 IoC 之后,要来理解 DI 就会方便多了。DI ,Dependency Injection,即依赖注入。具体地说就是:容器在应用程序运行时将对象的依赖资源(其他 bean 、配置、连接等)动态地注入进去。DI 其实是 IoC 的一种实现形式。前面的描述已经七七八八八了,这里偷懒就不再细说了。

 

1.4 依赖注入的 3种方式

1.4.1、构造器注入

  通过带参构造器,将相关依赖在对象实例化时进行设置。

public ComplexServiceA(ServiceA ServiceA, serviceA serviceB, ServiceC serviceC, ServiceD serviceD) {
    this.serviceA = ServiceA;
    this.serviceB = serviceB;
    this.serviceC = serviceC;
    this.serviceD = serviceD;
}

 

1.4.2、setter 方法注入

  这样就是把依赖当成普通的 bean 了,直接通过 setter 进行设置。类似于实例化之后进行属性的设置。

public void setServiceA(ServiceA serviceA ) {
    this.serviceA = serviceA;
}

 

1.4.3、接口注入

   有兴趣的可以参考:

 

1.4.4、其他方式

  另外需要说明的是,从 Spring 对 IoC 的说明来看,其实还有其他方式进行依赖注入,比如工厂方法等,方法都是类似的,就是能够返回指定的 bean 。

  

以上便是笔者对 IoC/DI 的一些大致理解。

 

2、参考

 

posted @ 2020-04-27 23:50  心明谭  阅读(395)  评论(0编辑  收藏  举报