上篇文章中我们介绍了准备Spring源码阅读环境的两种姿势,接下来,我们就要开始探寻这个著名框架背后的原理。Spring提供的最基本最底层的功能是bean容器,这其实是对IoC思想的应用,在学习Spring容器的实现原理之前,我们有必要先来了解一下什么是IoC,这就是本文的重点。

1. IoC

  IoC是随着近年来轻量级容器(Lightweight Container)的兴起而逐渐被很多人提起的一个名词,它的全称为Inversion of Control,中文通常译为控制反转。好莱坞原则"Don’t call us, we will call you."恰如其分地表达了“反转”的意味,是用来形容IoC最多的一句话。

  那么,为什么需要IoC?IoC的具体意义是什么?它到底有什么独到之处?让我们带着这些疑问开始我们的IoC之旅吧。

  为了更好的阐述IoC模式的概念,我们引入一下简单场景: 在经典的MVC项目中,Controller一般依赖多个Service来完成不同的业务逻辑,这里假设为serviceA和serviceB,如下代码所示:

// 示例代码1
public class ControllerA{
private ServiceA serviceA;
private ServiceB serviceB; public void doSomething(){ serviceA.doSomething(); serviceB.doSomething(); }
}

  通常情况下,我们可以直接在构造函数中构造这两个类,如下所示:

// 示例代码2
public ControllerA{
serviceA = new ServiceA();
serviceB = new ServiceB();
}

  如果我们依赖于某个类或服务,最简单而有效的方式就是直接在类的构造函数中新建相应的依赖类。我们是自己主动地去获取依赖的对象!

  可是回头想想,我们自己每次用到什么依赖对象都要主动地去获取,这是否真的必要?我们最终所要做的,其实就是直接调用依赖对象所提供的某项服务而已。只要用到这个依赖对象的时候,它能够准备就绪,我们完全可以不管这个对象是自己找来的还是别人送过来的。如果有人能够在我们需要时将某个依赖对象送过来,为什么还要大费周折地自己去折腾?

  实际上,IoC就是为了帮助我们避免之前的“大费周折”,而提供了更加轻松简洁的方式。它的反转,就反转在让你从原来的事必躬亲,转变为现在的享受服务。最简单的一种方式,体现在代码中,可以如下所示:

// 示例代码3
public ControllerA(ServiceA serviceA,ServiceB serviceB){
this.serviceA = serviceA;
this.serviceB = serviceB;
}

  通常情况下,被注入对象(ControllerA)会直接依赖于被依赖对象(ServiceA、ServiceB),也就是如上示例代码2中在构造函数中写死的情况。但是, 在IoC的场景中,二者之间是通过一个IoC Service Provider来打交道,所有的被注入对象和依赖对象由IoC Service Provider统一管理。体现在示例代码3中,是通过构造器传参来完成注入的(实际还可以通过setter、注解等方式完成注入)。

  如果增加新的需求,我们只需要增加一个实现了ServiceA的类就可以了,而不用重新增加一个Controller,而对于示例代码2,我们就不得不重写之前的所有代码了。这就是IoC的好处!

  如果要用一句话来概括IoC可以带给我们什么,那么我希望是,IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式!

  控制反转(IoC),体现在哪里,我觉得可以理解为:

  • 如果我们需要一个对象,从我们自己new出来转变成从IoC容器直接获取已经创建好的;

  • 某个对象依赖于别的对象,从对象在自己的构造器中new依赖的对象(示例代码2所示)转变成由IoC容器来注入依赖的对象(示例代码3所示);

  也许你会问,我也可以让IoC容器来调用示例代码2中的构造器来完成帮我注入啊,嗯,陈独秀同学,你挡到后面的同学了!

2. DI

  上一节讲了IoC的基本思想,在IoC模式中,依赖对象是需要通过IoC容器来帮助注入的,这就是依赖注入(Dependency Injection,简称DI)。而被注入对象总要通过某种方式来通知IoC容器为其提供相应服务吧,这里总结一下:

  • 构造方法注入;
  • setter方法注入;
  • 接口注入;

2.1 构造方法注入

  顾名思义,构造方法注入就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象,具体例子参考如上示例代码3。

  构造方法注入比较直观,对象被构造完成后即可进入就绪状态,可以马上使用。

2.2 setter方法注入

  对于JavaBean对象来说,通常会通过setXXX()和getXXX()方法来访问对应属性。这些setXXX()方法统称为setter方法,通过setter方法可以更改相应的对象属性。所以,某个对象只要为其依赖对所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中,如下简单示例:

// 示例代码1
public class ControllerA{
private ServiceA serviceA;
private ServiceB serviceB; public void setA(ServiceA serviceA){
this.serviceA = serviceA;
} public void setB(ServiceB serviceB){
this.serviceB = serviceB;
}
}

2.3 接口注入

  相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要IoC容器为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC容器最终通过这些接口来了解应该为被注入对象注入什么依赖对象,最典型的要数如BeanNameAware、BeanFactoryAware等接口了,客户对象实现了这些接口之后,容器会自动为其注入相应的资源如beanName、beanFactory。

2.4 三种注入方式的比较

  • 构造方法注入。这种注入方式的优点是,对象在构造完成之后就已进入就绪状态,可以马上使用。缺点就是,当依赖对象较多时,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难(这个在后面阅读源码时就能够体会到),维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动也可能造成维护上的不便。

  • setter方法注入。setter方法可以被继承,而且有良好的IDE支持。缺点是对象无法在构造完成后马上进入就绪状态。

  • 接口注入。从注入方式的使用上来说,接口注入是现在不甚提倡的一种方式,因为它强制被注入对象实现不必要的接口,带有侵入性,而构造方法注入和setter方法注入则不需要如此。

  综上,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在主流的注入方式;而接口注入因为侵入性较强,目前已经不流行了,仅仅Spring框架保留了部分接口注入,如BeanNameAware接口等。

3. IoC 容器

  虽然业务对象可以通过IOC方式声明相应的依赖,但是最终仍然需要通过某种角色或者服务将这些相互依赖的对象绑定到一起,而IoC容器(IoC Service Provider)就是对于IoC场景中的这一角色。

  IoC Service Provider只是一个抽象出来的概念,它可以指代任何将IoC场景中的业务对象绑定到一起的实现方式,可以是一段代码,也可以是一组相关的类,还可以是比较通用的IoC框架或者IoC容器实现,常见的比如Spring。

  IoC容器的职责相对来说比较简单,主要有两个:业务对象的构建管理和业务对象之间的依赖绑定。

  • 业务对象的构建管理。在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要完成,所以,IoC容器就承担了这个任务,从而避免这部分逻辑污染业务对象的实现。

  • 业务对象间的依赖绑定。对于IoC容器来说,这个职责是最艰巨也是最重要的,这是它的使命所在。IoC容器通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖对象注入绑定,从而保证每个业务对象在使用的时候可以处于就绪状态。

4. 总结

  本文主要介绍了IoC(控制反转)和DI(依赖注入)的概念,并探索验证了IoC所带给我们的部分"附加值"。在此基础上介绍了常见IoC容器Spring的基本职责,从而对IoC即IoC容器有了最基本认识。

Spring源码阅读笔记02:IOC基本概念的更多相关文章

  1. Spring源码阅读笔记

    前言 作为一个Java开发者,工作了几年后,越发觉力有点不从心了,技术的世界实在是太过于辽阔了,接触的东西越多,越感到前所未有的恐慌. 每天捣鼓这个捣鼓那个,结果回过头来,才发现这个也不通,那个也不精 ...

  2. Spring源码阅读笔记01:源码阅读环境准备

    1. 写在前面 对于做Java开发的同学来说,Spring就像是一条绕不过去的路,但是大多数也只是停留在对Spring的简单使用层面上,对于其背后的原理所知不多也不愿深究,关于这个问题,我在平时的生活 ...

  3. Spring源码阅读笔记03:xml配置读取

    前面的文章介绍了IOC的概念,Spring提供的bean容器即是对这一思想的具体实现,在接下来的几篇文章会侧重于探究这一bean容器是如何实现的.在此之前,先用一段话概括一下bean容器的基本工作原理 ...

  4. Spring源码阅读笔记05:自定义xml标签解析

    在上篇文章中,提到了在Spring中存在默认标签与自定义标签两种,并且详细分析了默认标签的解析,本文就来分析自定义标签的解析,像Spring中的AOP就是通过自定义标签来进行配置的,这里也是为后面学习 ...

  5. spring源码阅读笔记06:bean加载之准备创建bean

    上文中我们学习了bean加载的整个过程,我们知道从spring容器中获取单例bean时会先从缓存尝试获取,如果缓存中不存在已经加载的单例bean就需要从头开始bean的创建,而bean的创建过程是非常 ...

  6. spring源码阅读笔记08:bean加载之创建bean

    上文从整体视角分析了bean创建的流程,分析了Spring在bean创建之前所做的一些准备工作,并且简单分析了一下bean创建的过程,接下来就要详细分析bean创建的各个流程了,这是一个比较复杂的过程 ...

  7. spring源码阅读笔记10:bean生命周期

    前面的文章主要集中在分析Spring IOC容器部分的原理,这部分的核心逻辑是和bean创建及管理相关,对于单例bean的管理,从创建好到缓存起来再到销毁,其是有一个完整的生命周期,并且Spring也 ...

  8. Spring源码阅读笔记04:默认xml标签解析

    上文我们主要学习了Spring是如何获取xml配置文件并且将其转换成Document,我们知道xml文件是由各种标签组成,Spring需要将其解析成对应的配置信息.之前提到过Spring中的标签包括默 ...

  9. spring源码阅读笔记09:循环依赖

    前面的文章一直在研究Spring创建Bean的整个过程,创建一个bean是一个非常复杂的过程,而其中最难以理解的就是对循环依赖的处理,本文就来研究一下spring是如何处理循环依赖的. 1. 什么是循 ...

随机推荐

  1. python 进程信号量

    1.概念 信号量和锁相似,锁同一时间只允许一个对象(进程)通过,信号量同一时间允许多个对象(进程)通过 2.应用场景 多线程,并规定数量 3.格式: 导入信号量模块 实例化信号量对象,可以规定信号量的 ...

  2. path_info和get_full_path()的区别

    1.get_full_path() 获取的url路径包含参数 2.path_info 获取的路径不包含参数 注意:获取的路径都不包含协议 IP 和端口 3.补充 sesssion http://127 ...

  3. Djaingo 日志配置

    1.setting.py文件 # 项目级别的日志配置 BASE_LOG_DIR = os.path.join(BASE_DIR, "log") LOGGING = { 'versi ...

  4. Dynamics CRM CE 怎样从 UCI 改为 classic UI

    dynamics 现在大力推UCI. 但是对于大部分人来说还是使用不习惯. 怎样从UCI改为classic UI呢 1. 快速的方法 https://xxx.crm.dynamics.com/main ...

  5. jmeter使用—正则表达式提取器

    当我们测试接口的时候,有些请求参数是需要从上个接口返回获取的数据,这样的话我们就需要用到关联了.今天我们使用的关联是正则表达式提取器来进行获取需要关联的数据. 下面来具体说明正则表达式提取器的使用方法 ...

  6. mysql 基本常用语句

    1.展示当前数据库 所有表名(前提必须进入数据库,进入数据库语句:[use 数据库名;])mysql> show create table 表名; 2.看mysql支持哪些存储引擎:mysql& ...

  7. Nginx-入门(源码编译安装http://nginx.org/en/download.html)

    比较早的时候  web主要经典组合--->LAMP 近几年---->nginx后来居上--->LNMP=LEMP   Nginx = Engine x Nginx和Apache 都是 ...

  8. zabbix3.4搭建钉钉报警

    1.在钉钉群里添加一个自定义的机器 在设置说明中无需开启Outgoing机制 红色箭头指的信息很重要后面脚本会用到 2.脚本 #!/usr/bin/python # -*- coding: utf-8 ...

  9. Percona-XtraDB-Cluster-57 安装操作记录

    一.PXC集群的一些特性 Percona官网服务器位于境外,访问很困难.本次安装使用的是其官网提供的最新版本5.7.23-31.31.1.el7,当前日期为2018.10.10. PXC集群中,存储引 ...

  10. 手把手写框架入门(一) | 核心拦截器DispatchFilter实现

    前言 1Filter实现框架拦截 1配置自定义Filter 2创建一个Filter 3创建一个ActionMapping 4创建一个ActionMapper 5创建一个WebExecutor 6创建测 ...