思考 Spring 已经占据我们 Java 开发框架中的半壁江山了,从一开始工作我们就在使用 Spring。但是到底为什么要用 Spring,可能很多人都没有去思考过这个问题?许多人可能也疲于应对需求,无暇思考这种看似理所当然的问题。那今天,我们就好好来讨论一下究竟为什么要使用 Spring IoC? 逆向思考 假设在 ..

为什么要使用 SpringIOC?

思考

Spring 已经占据我们 Java 开发框架中的半壁江山了,从一开始工作我们就在使用 Spring。但是到底为什么要用 Spring,可能很多人都没有去思考过这个问题?许多人可能也疲于应对需求,无暇思考这种看似理所当然的问题。那今天,我们就好好来讨论一下究竟为什么要使用 Spring IoC?

逆向思考

假设在最初没有 Spring IoC 这种框架的时候,我们采用传统 MVC 的方式来开发一段常见的用户逻辑。

用户 DAO

public class UserDAO {

    private String database;

    public UserDAO(String dataBase) {
        this.database = dataBase;
    }
    public void doSomething() {
        System.out.println("保存用户!");
    }

}

用户 Service

public class UserService {

    private UserDAO dao;

    public UserService(UserDAO dao) {
        this.dao = dao;
    }
    public void doSomething() {
        dao.doSomething();
    }

}

用户 Controller

public class Controller {

    public UserService service;

    public Controller(UserService userService) {
        this.service = userService;
    }

    public void doSomething() {
        service.doSomething();
    }

}

接下来我们就必须手动一个一个创建对象,并将 dao、service、controller 依次组装起来,然后才能调用。

    public static void main(String[] args) {

        UserDAO dao = new UserDAO("mysql");
        UserService service = new UserService(dao);
        Controller controller = new Controller(service);

        controller.doSomething();

    }

分析一下这种做法的弊端有哪些呢?

  1. 在生成 Controller 的地方我们都必须先创建 dao 再创建 service 最后再创建 Controller,这么一条繁琐的创建过程。
  2. 在这三层结构当中,上层都需要知道下层是如何创建的,上层必须自己创建下层,这样就形成了紧密耦合。为什么业务程序员在写业务的时候却需要知道数据库的密码并自己创建 dao 呢?不仅如此,当如果 dao 的数据库密码变化了,在每一处生成 Controller 的地方都需要进行修改。
  3. 通过 new 关键字生成了具体的对象,这是一种硬编码的方式,违反了面向接口编程的原则。当有一天我们从 Hibernate 更换到 Mybatis 的时候,在每一处 new DAO 的地方,我们都需要进行更换。
  4. 我们频繁的创建对象,浪费了资源。

这时候我们再来看看如果用 SpringIOC 的情况,刚才的代码变成如下。

public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
        Controller controller = (Controller) context.getBean("controller");
        controller.doSomething();
        
    }

很明显,使用 IoC 之后,我们只管向容器索取所需的 Bean 即可。IoC 便解决了以下的痛点:

  1. Bean 之间的解耦,这种解耦体现在我们没有在代码中去硬编码 bean 之间的依赖。(不通过 new 操作依次构建对象,由 springIOC 内部帮助我们实现了依赖注入)。一方面,IoC 容器将通过 new 对象设置依赖的方式转变为运行期动态的进行设置依赖。
  2. IoC 容器天然地给我们提供了单例。
  3. 当需要更换 dao 的时候,我们只需要在配置文件中更换 dao 的实现类,完全不会破坏到之前的代码。
  4. 上层现在不需要知道下层是如何创建的。

通过这个例子可能读者有点若有所思了,那我们再来看一下 IoC 的定义是什么。

定义

控制反转(Inversion of Control,缩写为 IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称 DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

解读

控制反转,控制体现在什么地方?控制就体现在我们一开始的例子,上层需要去控制 new 下层对象。而反转意味着什么呢?
反转意味着我们把对象创建的控制权交出去,交给谁呢?交给 IoC 容器,由 IoC 容器负责创建特定对象,并填充到各个声明需要特定对象的地方。同学们可能会对 IoC 和 DI 觉得很绕,其实 IoC 相当于接口,它规定了要实现什么?而依赖注入(DI)就是具体的实现,它实现了 IoC 想要的效果。IoC 的思想很好地体现了面向对象设计法则之一——好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

如何减低耦合?我们举个例子,当在 IDEA 开发环境中我们添加插件的时候,却需要去知道该插件需要什么依赖,要怎么才能成功开启插件?是不是对用户相当不友好呢?既然我们要使用插件,意味着我们和插件之间是耦合的,无法避免。但是如果我们还需要知道插件需要的依赖和开启插件的步骤,说明我们和插件之间的耦合关系更强烈了。所以 SpringIOC 的定义当中写道“降低耦合”而非“消除耦合”,但只要耦合度降低的话,就有利于代码的维护和扩展。思考一下?是不是跟 Maven 的机制很类似呢?在 Maven 中声明依赖的话,Maven 的自动将该依赖所需的其他依赖递归的寻找。你可以把你自己想象成一个对象的设计师,你设计了一张对象内部构造的图纸,交给 SpringIOC,它会自动地根据这份图纸生成这个对象。

IoC 容器实现了开发中对象粒度的一种组件化,将每个对象当做组件一样放进容器当中。而每个组件都是可插拔,这种是最佳的对象解耦方式。一方面通过将对象之间的耦合关系从编译期推迟到运行期。一旦有需要这个对象的话,就直接使用,客户程序员完全不需要知道这个对象的来龙去脉。并且 IoC 容器对 Bean 的统一管理使得 AOP 的实现更加的方便。这样一来,我们客户程序员就可以更专注在业务代码的编写上。

笔者水平有限,希望这篇文章对读者有帮助。如有错误,烦请大家指出。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    607 引用 • 1209 回帖 • 820 关注
3 回帖   
请输入回帖内容...
  • Zephyr  

    我以为你要说为什么要用 Spring 的 IoC,原来是说 IoC

  • valarchie  

    [捂脸]我想通过 IoC 给我们开发者带来的好处来说明为什
    么使用 IoC。可能我没有理解到位,难以描述出来。[捂脸]

  • Zephyr  

    总结的挺好的,主要是老听见一些去 Spring 的声音,看到标题就以为是说为什么要用 Spring 了😂