回雁楼--Spring系列之IOC理论详解

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

IoC(Inversion of Control,控制反转) 是Spring 中一个非常重要的概念,它不是什么技术,而是一种解耦的设计思想。它的主要目的是借助于“第三方”(Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦(IOC容器管理对象,你只管使用即可),从而降低代码之间的耦合度。IOC 是一个原则,而不是一个模式,以下模式(但不限于)实现了IoC原则。
在这里插入图片描述
Spring IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。

在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

关于Spring IOC 的详细理解如下:

我们去理解控制反转(IoC)的时候,首先得带大家理解一下软件设计中的依赖倒置原则。

什么是依赖倒置原则:

假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。

这样的设计看起来没问题,但是可维护性却很低。假设设计完工之后,上司却突然说根据市场需求的变动,要我们把车子的轮子设计都改大一码。这下我们就蛋疼了:因为我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改;同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改——整个设计几乎都得改!我们现在换一种思路。我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身, 车身依赖汽车。

这时候,上司再说要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。

控制反转:

控制反转(Inversion of Control) 就是依赖倒置原则的一种代码设计的思路。具体采用的方法就是所谓的依赖注入(Dependency Injection)。这几种概念的关系大概如下:在这里插入图片描述
去理解这几个概念,还是用上面汽车的例子。这次写成代码。我们先定义四个Class,车,车身,底盘,轮胎。然后初始化这辆车,最后跑这辆车。代码如下:

    //车模型
    Class Car{
        private Framework framework;
        
        Car(){
            this.framework = new Framework();
        }
        public void run(){
            //....
        }
    }

    //车身框架
    Class Framework{
        private Bottom bottom;

        Framework(){
            this.bottom = new Bottom();
        }
    }

    //车底盘
    Class Bottom{
        private Tire tire;

        Bottom(){
            this.tire = new Tire();
        }
    }

    //轮胎
    Class Tire{
        private int size;

        Tire(){
            this.size = 30;
        }
    }
    
    //初始化车
    Car mycar = new Car();
	//运行
    mycar.run();

这里相当于上面第一个例子,上层建筑依赖下层建筑——每一个类的构造函数都直接调用了底层代码的构造函数。假设我们需要改动一下轮胎(Tire)类,把它的尺寸变成动态的,而不是一直都是30。我们需要这样改:
Tire类修改:

 	//轮胎
    Class Tire{
        private int size;

        Tire(int size){
            this.size = size;
        }
    }

由于我们修改了轮胎的定义,为了让整个程序正常运行,我们需要做以下改动

	//车模型
    Class Car{
        private Framework framework;

        Car(int size){
            this.framework = new Framework(size);
        }
        public void run(){
            //....
        }
    }

    //车身框架
    Class Framework{
        private Bottom bottom;

        Framework(int size){
            this.bottom = new Bottom(size);
        }
    }

    //车底盘
    Class Bottom{
        private Tire tire;

        Bottom(int size){
            this.tire = new Tire(size);
        }
    }

    //轮胎
    Class Tire{
        private int size;

        Tire(int size){
            this.size = size;
        }
    }

    //初始化车
    int size = 40;
    Car mycar = new Car(size);

    mycar.run();

由此我们可以看到,仅仅是为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数!在软件工程中,这样的设计几乎是不可维护的——在实际工程项目中,有的类可能会是几千个类的底层,如果每次修改这个类,我们都要修改所有以它作为依赖的类,那软件的维护成本就太高了。

所以我们需要进行控制反转(IoC),及上层控制下层,而不是下层控制着上层。我们用依赖注入(Dependency Injection)这种方式来实现控制反转。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。这里我们用构造方法传递的依赖注入方式重新写车类的定义:

    //车模型
    Class Car{
        private Framework framework;

        Car(Framework framework){
            this.framework = framework;
        }
        public void run(){
            //....
        }
    }

    //车身框架
    Class Framework{
        private Bottom bottom;

        Framework(Bottom bottom){
            this.bottom = bottom;
        }
    }

    //车底盘
    Class Bottom{
        private Tire tire;

        Bottom(Tire tire){
            this.tire = tire;
        }
    }

    //轮胎
    Class Tire{
        private int size;

        Tire(){
            this.size = 30;
        }
    }

    //初始化车
    Tire tire = new Tire();
    Bottom bottom = new Bottom(tire);
    Framework framework = new Framework(bottom);
    Car mycar = new Car(framework);
    //运行
    mycar.run();

然后再做之前一样的操作对轮胎处的代码进行动态修改:

    //车模型
    Class Car{
        private Framework framework;

        Car(Framework framework){
            this.framework = framework;
        }
        public void run(){
            //....
        }
    }

    //车身框架
    Class Framework{
        private Bottom bottom;

        Framework(Bottom bottom){
            this.bottom = bottom;
        }
    }

    //车底盘
    Class Bottom{
        private Tire tire;

        Bottom(Tire tire){
            this.tire = tire;
        }
    }

    //轮胎
    Class Tire{
        private int size;

        Tire(int size){
            this.size = size;
        }
    }

    //初始化车
    int size = 40;
    Tire tire = new Tire(size);
    Bottom bottom = new Bottom(tire);
    Framework framework = new Framework(bottom);
    Car mycar = new Car(framework);
    //运行
    mycar.run();

通过上面代码的比对,会发现这里只需要修改轮胎类就行了,不用修改其他任何上层类。这显然是更容易维护的代码。不仅如此,在实际的工程中,这种设计模式还有利于不同组的协同合作和单元测试:比如开发这四个类的分别是四个不同的组,那么只要定义好了接口,四个不同的组可以同时进行开发而不相互受限制;而对于单元测试,如果我们要写Car类的单元测试,就只需要Mock一下Framework类传入Car就行了,而不用把Framework, Bottom, Tire全部new一遍再来构造Car。这里我们是采用的构造函数传入的方式进行的依赖注入。其实还有另外两种方法:Setter传递接口传递。这里就不多讲了,核心思路都是一样的,都是为了实现控制反转。

到这里应该能理解什么控制反转和依赖注入了。在上面举的代码里,什么地方是控制反转容器(IoC Container)呢?其实就是对车类进行初始化的那段代码发生的地方,就是控制反转容器。

显然你也应该观察到了,因为采用了依赖注入,在初始化的过程中就不可避免的会写大量的new。这里IoC容器就解决了这个问题。这个容器可以自动对你的代码进行初始化,你只需要维护一个Configuration(可以是xml可以是一段代码),而不用每次初始化一辆车都要亲手去写那一大段初始化的代码。这是引入IoC Container的第一个好处。IoC Container的第二个好处是:我们在创建实例的时候不需要了解其中的细节。在上面的例子中,我们自己手动创建一个车instance时候,是从底层往上层new的:
在这里插入图片描述
这个过程中,我们需要了解整个Car/Framework/Bottom/Tire类构造函数是怎么定义的,才能一步一步new/注入。而IoC Container在进行这个工作的时候是反过来的,它先从最上层开始往下找依赖关系,到达最底层之后再往上一步一步new(有点像深度优先遍历):
在这里插入图片描述
IoC 容器这里可以直接隐藏具体创建实例的细节,其实看起来就像一个工厂
在这里插入图片描述
我们就像是工厂的客户。我们只需要向工厂请求一个Car实例,然后它就给我们按照Config创建了一个Car实例。我们完全不用管这个Car实例是怎么一步一步被创建出来。

实际项目中,有的Service Class可能是十年前写的,有几百个类作为它的底层。假设我们新写的一个API需要实例化这个Service,我们总不可能回头去搞清楚这几百个类的构造函数吧?IoC Container的这个特性就很完美的解决了这类问题——因为这个架构要求你在写class的时候需要写相应的Config文件,所以你要初始化很久以前的Service类的时候,前人都已经写好了Config文件,你直接在需要用的地方注入这个Service就可以了。这大大增加了项目的可维护性且降低了开发难度。

讲到这里本章对Spring系列的IOC理论详解也就结束了,如果想了解更多知识可以在对应的专栏中看系列文章,谢谢大家的观看,希望能给各位同学带来帮助。如果觉得博主写的还可以的,可以点赞收藏。 😉

posted @ 2020-12-14 14:28  奋斗的小宋  阅读(41)  评论(0)    收藏  举报