文章目錄
  1. 1. Spring的IOC理解
  2. 2. 什么是IOC
  3. 3. 最简单的实现
  4. 4. 总结

Spring的IOC理解

什么是IOC

在这里我们不谈Spring的基础知识,我们知道谈到Spring就会谈到IOC,这个IOC是什么呢,中文名叫控制反转,这个东西是伴随着一些编程思想出现,其实同Java的本身也有关

就好比我熟悉的Python就是一个鸭子语言,你可以随便把一个值丢掉函数里面去,只要他满足一些特性就能正常运行,但是Java是一种强类型语言,你函数给什么参数,必须传什么参数

这里就不讨论两张语言的设计优劣呢,Java这种特性也做了一些妥协,我们肯定得为语言的扩展性做点事,谁也不知道未来会发生什么,Java里面使用多态来实现这种扩展,只要他是函数参数的家族成员,他就能上去运行

这个多态是实现IOC的基础,但是造成他出现的原因是因为设计模式里面的单一职责原则,这个要求我们类功能要单一,我们这里给一个例子来说明这个问题

class Car {
    void run() {
        System.out.println("Car running...");
    }
}

首先我们有一个Car的类,一开始我们只让他有run这个属性,很好,接下来我们想知道是谁驾驶这辆车,于是我们便给这个类加一个字段driver

public class Car {
    String driver;
    public Car(String driver) {
        this.driver = driver;
    }
    void run() {
        System.out.println("Driver :" + driver);
        System.out.println("Car running...");
    }
}

很好我们知道驾驶这辆车的人,接着我们又想知道这个驾驶人的驾龄,如果我们继续给Car加入字段,这样我们就违背了单一职责原则,Car类不但承担了车的功能还承担了人的功能

于是我们就把驾驶人隔离出来

class Driver{
    String name;
    String age;
    public Driver(String name, String age) {
        this.name = name;
        this.age = age;
    }
}


class Car {
    Driver driver;
    public Car(Driver driver) {
        this.driver = driver;
    }
    void run() {
        System.out.println("Driver age:" + driver.age + "name: " + driver.name);
        System.out.println("Car running...");
    }
}

我们重新将类分成两个类来实现了这个问题,但是这个时候又来了一个问题,我们有一个飞行员的也想驾驶这辆车,但是这辆车只能司机来驾驶,但是飞行员和司机开车的动作步骤是一样的,为了复用run这个函数,你开始揪起了你的头发.

你想呀想突然想到,Java的多态,假如我们声明一个IDriver的接口,让飞行员和司机都继承这个类这样我们只要给车一个IDriver对象就能复用run函数

//IDriver.java
public interface IDriver{
    String getName();
    void setName(String name);
    int getAge();
    void setAge(int age);
}

// Driver.java
public class Driver implements IDriver{
    String name;
    int age;


    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int getAge() {
        return this.age;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }
}



// Aviator.java
public class Aviator implements IDriver{
    String name;
    int age;


    @Override
    public String getName() {
        return null;
    }

    @Override
    public void setName(String name) {

    }

    @Override
    public int getAge() {
        return 0;
    }

    @Override
    public void setAge(int age) {

    }
}


//Car.java
public class Car {
    private IDriver driver;

    public void setDriver(IDriver driver) {
        this.driver = driver;
    }

    void run() {
        System.out.println("Driver age: " + driver.getAge() + " name: " + driver.getName());
        System.out.println("Car running...");
    }
}

我们重构代码把Driver抽象为接口,然后让司机和飞行员都继承它,这样不管我们再添加什么其他的人就能适配这辆车.这个就是依赖倒置(DI)的思想

网上大部分教程就停留到这里了,这里我们继续探索下去,看看Spring是如何让这个DI更加简单的

首先我们反思一下,我们使用接口参数让我们的代码符合了设计模式,但是也带来了一些繁琐,我们来用代码”开”这辆车

IDriver driver = new Driver();
driver.setName("allen");
driver.setAge(18);
Car car = new Car();
car.setDriver(driver);
car.run();

PS:当然可以把赋值放到构造器中减少代码,但是由于Bean依赖方法接口来赋值,所以为了后面讲解Bean这里就不采用构造器来减少代码

代码有2行变成了6行,而且我们发现这个代码现在带来两个问题:

  1. 每次运行都得创建一个实现IDriver的对象
  2. 每次我们想换人开车的时候都得修改源代码

而且这些工作都很繁琐,作为一个偷懒的程序员,我可不想给每个用户都重新写一套代码,我们的想法很简单,我们希望这个Car能够开箱即用,其实前面我们已经实现了控制反转了,现在就是要解决控制反转带来的“负面影响”

而且我们发现了一个问题,假如我们把上面函数放到一个代码里面,每次我们“开车”都得创建一个司机,然而我们还是相信“老司机”的手艺,所以我们也希望是否能够”记住“司机,只让一个老司机开车

接下来就是隆重介绍SpringBean的用法了,前面我们知道我们需要某种机制来去除”IOC“的弊端,我们把每个Car当做一个对象,其实我们需要一个配置文件来记录IDriver这些依赖对象,对象的其实在Java里面表现就是一棵树,所以通俗来讲我们需一个”树结构“数据来存贮依赖关系

我们程序在运行的时候解析这个树结构,然后依次给对象注入你想给他实例话的对象(比如你把”IDriver“设置为飞行员),这样的话,我们把依赖关系成功放到了配置文件中

这样带来两个好处:

  1. 想给不同用户使用软件时候,源代码不需要改变,只要给他们不同的配置文件就行
  2. 我们可以保存依赖实现”老司机“的复用

所以现在我们理理思路,我们需要的有两个东西

  1. 配置文件
  2. 一个加载配置文件并保存依赖的对象

SpringBean中这两个分别对应xml文件和实现ResourceLoader接口对象(有多种实现)

为了更好的理解Bean,接下来我们就从代码出发来测试这个Bean

最简单的实现

首先我们新建一个Spring项目,无论你是用IntelliJ还是Eslipse都没关系,只能你能引用到Spring库就行,我们复用前面的代码,看看使用Spring Bean来如何解决掉IOC的”副作用“

我们把前面的类分别放到同一路径不同的文件夹中,接下来我们先创建一个xml文件,什么名字不重要,我们这里先命名为driver.xml

<?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="car" class="Car">
        <property name="driver">
            <bean class="Driver">
                <property name="name" value="Allen"></property>
                <property name="age" value="18"></property>
            </bean>
        </property>

    </bean>
</beans>

写入这些东西,接下来我们看看是否能够通过这个xml文件来直接得到一个配置好司机Allen的车

随便新建一个类在上面的路径中,我们这里就新建一个Main

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("driver.xml");
        Car car  = context.getBean("car", Car.class);
        car.run();

    }
}

输出为

Driver age:18 name: Allen
Car running...

我们成功通过一个xml配置文件和一个ApplicationContext对象实现了一个即开即走的车,而且假如我们想换个司机,我们可以修改配置文件把class换成“飞行员”,而且我们可以发现我们得到的司机都是一样的,验证方法很简单,我就不写代码了,假如我们想换司机怎么办,简单在bean里面加上scope=“prototype”就行(默认值为singleton

接下来我们又有一个疑问,假如我们有一辆特别宝马车,我们希望只有某一种加上员能能开(假设只有飞行员),也就是是说,我们其实即不想放弃IOC,但是又不想将这个配置写到Bean里面去,有办法能够解决吗?

当然有,Spring2.5就支持注解来写Bean配置,对于一些固定的类,我们可以把依赖关系用代码写到类中,这样一方面能够保证IOC,一方面又能实现Bean xml文件瘦身

由于Spring默认不会去扫描注解,所以有三种方式,第一种是在xml里面用加上一个

<context:component-scan base-package="...."></context:component-scan>

第二种是使用AnnotationConfigApplicationContext来对象来进行扫描,第三种就是SpringApplication来运行Spring程序自动扫描

这三种方式假如你最后要做一个web程序的话,第三种是非常方便的,这里我们就不谈怎么使用注解来代替xml文件了,本质上是一样的,其实在我没有理解Bean的强大之前,我比较推崇使用注解来写Bean,但是随着对Bean的探索,我发现xml文件才是最佳选择,他将程序依赖与代码分离开来,假如我们还想用程序依赖写在代码里面,那就违背了Bean的设计初衷

如果你想了解怎么使用注解可以阅读这篇博客

总结

至此,我们从问题的出现到问题的解决探索了IOC背后的故事,但是你可能会有一个疑问,为什么Spring里面会有IOC问题。

其实这个也跟Web的发展有关,我们知道从Web的发展,一开始是没有前端的,只有后端,慢慢的后端分离出来前端,Web端页面也被分离出视图层和数据层,随着逐渐分离,也就出现我们前面举到的例子,类越来越多,比如视图层依赖数据层,数据层依赖控制层…..

这种层层依赖的问题延生出来的IOC的提出,也就慢慢的促进了Bean这个库的开发,也正是因为Bean我们才能享受静态强类型语言的低耦合的酸爽。

文章目錄
  1. 1. Spring的IOC理解
  2. 2. 什么是IOC
  3. 3. 最简单的实现
  4. 4. 总结