• 为什么要用IOC:inversion of controll反转控制(把创建对象的权利交给框架)


    作为一名 Java 开发,对 Spring 框架是再熟悉不过的了。Spring 支持的控制反转(Inversion of Control,缩写为IoC)早已成为我们的开发习惯,仿佛 Java 开发天生就该如此。人总是会忽略习以为常的事物,所有人都熟练使用 IoC ,却鲜有人说得清楚到底为什么要用 IoC。

    我们现在假设回到了没有 IoC 的时代,用传统的 Servlet 进行开发。

    1. 传统开发模式的弊端

    三层架构是经典的开发模式,我们一般将视图控制、业务逻辑和数据库操作分别抽离出来单独形成一个类,这样各个职责就非常清晰且易于复用和维护。大致代码如下:

    @WebServlet("/user")
    public class UserServlet extends HttpServlet {
        // 用于执行业务逻辑的对象
        private UserService userService = new UserServiceImpl();
        
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // ...省略其他代码
                
            // 执行业务逻辑
            userService.doService();
            
            // ...返回页面视图
        }
    }

    service调用dao

    public class UserServiceImpl implements UserService{
        // 用于操作数据库的对象
        private UserDao userDao = new UserDaoImpl();
        
        @Override
        public void doService() {
            // ...省略业务逻辑代码
                
            // 执行数据库操作
            userDao.doUpdate();
            
            // ...省略业务逻辑代码
        }
    }

    dao

    public class UserDaoImpl implements UserDao{
        @Override
        public void doUpdate() {
            // ...省略JDBC代码
        }
    }

    上层依赖下层的抽象,代码就分为了三层:

     业界普遍按这种分层方式组织代码,其核心思想是职责分离。层次越低复用程度越高,比如一个 DAO 对象往往会被多个 Service 对象使用,一个 Service 对象往往也会被多个 Controller 对象使用

     条理分明,井然有序。这些被复用的对象就像一个个的组件,供多方使用。

    虽然这个倒三角看上去非常漂亮,然而我们目前的代码有一个比较大的问题,那就是我们只做到了逻辑复用,并没有做到资源复用

    上层调用下一层时,必然会持有下一层的对象引用,即成员变量。目前我们每一个成员变量都会实例化一个对象,如下图所示:

     每一个链路都创建了同样的对象,造成了极大的资源浪费本应多个 Controller 复用同一个 Service,多个 Service 复用同一个 DAO。现在变成了一个 Controller创建多个重复的 Service,多个 Service 又创建了多个重复的 DAO,从倒三角变成了正三角。

    许多组件只需要实例化一个对象就够了,创建多个没有任何意义。针对对象重复创建的问题,我们自然而然想到了单例模式。只要编写类时都将其写为单例,这样就避免了多例模式造成的资源浪费。但是,引入设计模式必然会带来复杂性,况且还是每一个类都为单例,每一个类都会有相似的代码,其弊端不言自明。

    有人可能会说,那我不在意“这点”资源浪费了,我服务器内存大无所谓,我只求开发便捷痛快不想写额外的代码。

    确实,三层架构达到逻辑复用已经非常方便了,还奢求其他的干什么呢。但就算不管资源问题,目前代码还有一个致命缺陷,那就是变化实现类的代价太大

    假设有 10 个 Controller 依赖了 UserService,最开始实例化的是 UserServiceImpl,后面需要换一个实现类 OtherUserServiceImpl,我就得逐个修改那 10 个 Controller,非常麻烦。更换实现类的需求可能不会太多,没多大说服力。那咱们看另一个情况。

    之前咱们演示的组件创建过程非常简单,new 一下就完了,可很多时候创建一个组件没那么容易。比如 DAO 对象要依赖一个这样的数据源组件:

    public class UserDaoImpl implements UserDao{
        private MyDataSource dataSource;
    
        public UserDaoImpl() {
            // 构造数据源
            dataSource = new MyDataSource("jdbc:mysql://localhost:3306/test", "root", "password");
            // 进行一些其他配置
            dataSource.setInitiaSize(10);
            dataSource.setMaxActive(100);
            // ...省略更多配置项
        }
    }

    该数据源组件要想真正生效需要对其进行许多配置,这个创建和配置过程是非常麻烦的。而且配置可能会随着业务需求的变化经常更改,这时候你就需要修改每一个依赖该组件的地方,牵一发而动全身。这还只是演示了一个数据源的创建配置过程,真实开发中可有太多组件和太多配置需要编码了,其麻烦程度堪称恐怖。

    当然,这些问题都可以引入设计模式来解决,不过这样一来又绕回去了:设计模式本身也会带来复杂性。这就像一种死循环:传统开发模式编码复杂,要想解决这种复杂却得陷入另一种复杂。难道没有办法解决了吗?当然不是的,在讲优秀解决方案前,我们先来梳理一下目前出现的问题:

    • 创建了许多重复对象(多例模式),造成大量资源浪费;
    • 更换实现类需要改动多个地方;
    • 创建和配置组件工作繁杂,给组件调用方带来极大不便。

    透过现象看本质,这些问题的出现都是同一个原因:组件的调用方参与了组件的创建和配置工作

     其实调用方只需关注组件如何调用,至于这个组件如何创建和配置又与调用方有什么关系呢?就好比我去餐馆只需点菜,饭菜并不需要我亲自去做,餐馆自然会做好给我送过来。如果我们编码时,有一个「东西」能帮助我们创建和配置好那些组件,我们只负责调用该多好。这个「东西」就是容器。

    容器这一概念我们已接触过,Tomcat 就是 Servlet 的容器,它帮我们创建并配置好 Servlet,我们只需编写业务逻辑即可。试想一下,如果 Servlet 要我们自己创建,HttpRequest、HttpResponse 对象也需要我们自己配置,那代码量得有多恐怖。

    Tomcat 是 Servlet 容器,只负责管理 Servlet。我们平常使用的组件则需要另一种容器来管理,这种容器我们称之为 IoC 容器。

    现在只能用一次newInstance(),对象创建完后应该马上存起来,定义一个Map<String,Object>,用于存放我们要创建的对象,我们把它称之为容器

     如何实现单例对象呢?

     如何实现只创建一个对象?

    答:静态代码块只在类加载的时候执行一次

    对象创建完成之后为什么要放入Map中?

    答:因为由于java的垃圾回收机制,对象就会在长时间不用时被回收,当下次再用的时候肯定就没有了。所以创建完了之后存起来

    如果我们自己写代码,降低依赖的方式是用工厂模式来实现,BeanFactory类这段代码并没有难度,如果在实际开发中我们自己来写的话,肯定会消耗更多的精力,所以我们把这一段内容完全交给了spring使用spring中的IOC降低程序间的依赖关系,即解耦

     使用new创建对象和使用工厂创建对象的区别:

    上面两行代码代表了两种截然不同的创建对象的方式,第一种创建对象的方式是采用new的方式,是主动的,应用直接跟资源联系,他们之间有一个必然的联系,且联系消除不掉。从这一点来说,对于资源的独立就变得很难了,因为有明显的依赖关系,

    而当我们采用第二种方式创建对象的时候,App断开了与资源的联系,而是找工厂要一个资源,由工厂负责与资源取得联系,并把对象转到应用中,这种思想就是我们说的IOC,为什么叫控制反转而不叫降低依赖呢?

    我们在编写service的时候,AccountServiceImpl可以选择new关键字来创建accountDao对象,也可以使用工厂来创建accountDao对象,但是AccountServiceImpl类把自主创建accountDao对象的权利交个BeanFactory类,BeanFactory类通过一个固定的名称获取Bean对象,这种控制权发生的转移就叫做控制反转,带来的好处就是降低程序间的依赖关系,削减程序的耦合。注意削减只能是降低,并不能完全的消除,如果两个类之间的什么关系都没有,那么有一个类就是多余的。

    控制反转:把创建对象的权利交给框架。实现类已经创建好了,只是把创建对象的工作交给spring而已。

    控制反转和依赖注入的关系

    控制反转,是指对象的创建和配置的控制权从调用方转移给容器。好比在家自己做菜,菜的味道全部由自己控制;去餐馆吃饭,菜的味道则是交由餐馆控制。IoC 容器就担任了餐馆的角色。

    有了 IoC 容器,我们可以将对象交由容器管理,交由容器管理后的对象称之为 Bean。调用方不再负责组件的创建,要使用组件时直接获取 Bean 即可:

    @Component
    public class UserServiceImpl implements UserService{
        @Autowired // 获取 Bean
        private UserDao userDao;
    }

    调用方只需按照约定声明依赖项,所需要的 Bean 就自动配置完毕了,就好像在调用方外部注入了一个依赖项给其使用,所以这种方式称之为 依赖注入(Dependency Injection,缩写为 DI)。控制反转和依赖注入是一体两面,都是同一种开发模式的表现形式。

    IoC 轻而易举地解决了我们刚刚总结的问题:

    1、对象交由容器管理后,默认是单例的,这就解决了多例模式造成的资源浪费问题。

    2、若要更换实现类,只需更改 Bean 的声明配置,即可达到无感知更换:

    public class UserServiceImpl implements UserService{
        ...
    }
    
    // 将该实现类声明为 Bean
    @Component
    public class OtherUserServiceImpl implements UserService{
        ...
    }

    现在组件的使用和组件的创建与配置完全分离开来。调用方只需调用组件而无需关心其他工作,这极大提高了我们的开发效率,也让整个应用充满了灵活性、扩展性。

  • 相关阅读:
    汉文博士 0.5.6.2345 修订版发布
    汉文博士 0.5.6 正式版发布
    汉文博士 0.5.5 正式版发布
    汉文博士新测试版发布(0.5.4.2228)
    海盗(Haidao)网店系统最新官方版
    ZipMarket数字内容/素材交易网站源码项目
    windows phone 8 使用页面传对象的方式 实现页面间的多值传递
    仿win8磁贴界面以及功能
    三角形状的点阵模糊效果iOS源码
    Coding iOS客户端应用源码
  • 原文地址:https://www.cnblogs.com/zwh0910/p/14609021.html
Copyright © 2020-2023  润新知