阅读王福强老师《Spring揭秘》一书的读书笔记,记录主要知识点,方便后续查阅
IOC容器
一、IoC的基本概念
依赖注入
通常情况下,被注入对象会直接依赖于被依赖对象。但在IoC场景中,二者之间通过IoC Service Provider来打交道,所有被注入对象和依赖对象现在都由IoC Service Provider来统一管理,被注入对象需要什么直接找IoC Service Provider要,后者就会直接把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider为被注入对象服务的目的,IoC Service Provider在这里就是充当IoC容器的角色
从被注入对象的角度来看,与之前直接寻找依赖对象相比,依赖对象的获取方式发生了反转,控制权从被注入对象转移到了IoC Service Provider那里
依赖注入的方式
构造方法注入
- 被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象
- IoC Service Provider会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。同一个对象是不能被构造两次的,所以被注入对象的构造乃至整个生命周期,应该是由IoC Service Provider来管理的
- 这种注入方式比较直观,对象构造完成后就进入就绪状态,可以马上使用
setter方法注入
- 对于JavaBean对象来说,一般会通过getXXX()和setXXX()方法来访问对应属性。setXXX()方法统称setter方法,用于更改相应的对象属性;getXXX()方法统称getter方法,用于获取相应属性的状态
- 当前对象只要为其依赖对象所对应的属性添加setter方法就可以通过setter方法将相应的依赖对象设置到被注入对象中
- setter方法注入虽然不像构造方法注入那样在对象构造完成后即可使用,但是相对来说更宽松一些,可以在对象构造完成后再注入
接口注入
相对于前两种方式,接口注入相对比较繁琐。被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法专门用于为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该被注入对象注入什么依赖对象
FXNewsProvider是被注入对象,需要为其注入依赖的IFXNewsListener
FXNewsProvider为了让IoC Service Provider为其注入所依赖的IFXNewsListener,首先需要实现FXNewsListenerCallable接口,这个接口会声明一个injectNewsListener方法(方法名称随意),该方法的参数就是依赖对象的类型。这样,injectionServiceContainer对象(即对应的IoC Service Provider)就可以通过这个接口方法将依赖对象注入到被注入对象FXNewsProvider中
注意: 在这种情况下,实现的接口和接口中声明的方法名称都不重要。重要的是接口中声明方法的参数类型必须是“被注入对象”所依赖对象的类型
相对于前两种依赖注入方式,接口注入比较死板和繁琐。如果需要注入依赖对象,被注入对象就必须声明和实现另外的接口。就好像在酒吧点啤酒,为了让服务生理解你的意思,你就必须戴上一顶啤酒杯式的帽子,多此一举
三种注入方式的比较
- 接口注入
- 不提倡使用
- 强制被注入对象实现不必要的接口,带有侵入性
- 构造方法注入
- 优点
- 对象在构造完成后就进入就绪状态,可以马上使用
- 缺点
- 当依赖对象较多时,构造方法参数列表会比较长
- 通过反射构造对象时,对相同类型的参数处理会比较困难,维护和使用也比较麻烦
- Java中,构造方法不能被继承,无法设置默认值,对于非必要的依赖处理可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便
- 优点
- setter方法注入
- 优点
- 方法可以命名,所以setter方法注入在描述性上要比构造方法好一点
- setter方法可以被继承,允许设置默认值,有良好的IDE支持
- 缺点
- 对象构造完成后不能马上进入就绪状态
- 优点
综上所述,构造方法注入和setter方法注入因为侵入性较弱,且易于理解和使用,所以是使用最多的注入方式;而接口注入因为侵入性较强,现在几乎不用了
IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式
二、IoC Service Provider
业务对象可以通过IoC的方式声明相应的依赖,但是最终还是需要通过某种角色或者服务将这些相互依赖的对象绑定到一起,IoC Service Provider就对应IoC场景中的这一角色
IoC Service Provider是一个抽象出来的概念,它可以指代任何将IoC场景中的业务对象绑定到一起的实现方式。它可以是一段代码,也可以是一组相关的类,甚至可以是比较通用的IoC框架或者IoC容器实现
主要作用
业务对象的构建
业务对象不需要关系所依赖的对象是如何创建的,但是这部分工作始终需要有人来做。所以,IoC Service Provider 需要将对象的构建逻辑从客户端对象(对象A需要引用对象B,那么A就是B的客户端对象,不管A处于service层还是数据访问层)那里剥离出来,以免这部分逻辑污染业务对象的实现
业务对象间的依赖绑定
是IoC Service Provider最重要的功能。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态
管理对象间的依赖关系
IoC Service Provider想要为被注入对象提供依赖注入,必须知道自己所管理和掌握的被注入对象和依赖对象之间的对应关系,IoC Service Provider常用记录这些对应关系的方式有下面几种
直接编码
容器启动前,通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们之间的依赖注入关系
FXNewsProvider对象创建依赖于IFXNewsListener接口对象
- 构造方法注入或者setter方法注入
通过为想要的类指定对应的具体实例,当我们需要这种类型的对象实例时,让IoC容器将其容器中注册的、对应的那个具体实例返回给我们
- 接口注入
需要将“注入标志接口”与相应的依赖对象进行绑定,让容器知道具体对应关系
通过bind方法将“被注入对象”(由IFXNewsListenerCallable接口添加标志)所依赖的对象,绑定为容器中已注册的IFXNewsListener类型的对象实例
容器在返回FXNewsProvider对象实例之前,会根据这个绑定信息将IFXNewsListener注册到容器中的对象实例注入到“被注入对象”(FXNewsProvider)中,并最终返回已经组装完毕的FXNewsProvider对象
配置文件
是较为普遍的依赖注入关系管理方式,像普通文本文件、properties文件、XML文件等都可以成为管理依赖注入关系的载体。不过最常见的还是通过XML文件来管理对象注册和对象间的依赖关系
最后通过“newsProvider”这个beanName就可以从容器中获取已经组装好的FXNewsProvider并直接使用
元数据
代表实现是Google Guice框架(基于注解和Generic的基础上开发的IoC框架),直接在类中使用元数据信息来标注各个对象之间的依赖关系,然后由Guice框架根据这些注解所提供的信息将对象组装后交给客户端对象使用
通过@Inject指明需要IoC Service Provider通过构造方法注入方式为FXNewsProvider注入其所依赖的对象。其他的依赖相关信息,在Guice中是由相应的Module来提供的
通过Module指定进一步的依赖信息(需要依赖IFXNewsListener类型和IFWNewsPersister类型的实例对象)后,就可以直接从Guice容器那里获取最终注入完毕并可以直接使用的对象了
注解最终也是通过代码处理来确定最终的注入关系,从这点来水,注解方式可以算编码方式的一种特殊情况
三、BeanFactory
Spring提供了两种容器类型
- BeanFactory。基础类型IoC容器,提供完整的IoC服务支持
- 默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器内某个受管对象时,才对该受管对象进行初始化以及依赖注入操作
- 相对来说,懒加载,所以容器启动速度比较快
- 所需资源有限,并且功能要求不是特别严格的场景,适合选用IoC容器
- ApplicationContext。在BeanFactory的基础上构建的,是相对比较高级的容器实现
- 除了拥有BeanFactory的所有支持,还提供了其他高级特性(国际化信息支持、事件发布等)
- 所管理的对象,在该类型容器启动后默认全部初始化并绑定完成
- 要求更多的系统资源,启动时完成所有初始化,启动时间相对更长
- 适用于系统资源充足,要求更多功能的场景
ApplicationContext间接继承了BeanFactory,没有特殊指明的情况下,BeanFactory为中心的内容同样适用于ApplicationContext
BeanFactory就是一个生产Bean的工厂,Spring中的每个业务对象看作是一个JavaBean对象(所以Spring中的IoC基本容器叫BeanFactory这个名字)
BeanFactory可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象间依赖关系的绑定
BeanFactory的使用
使用BeanFactory之后,业务对象间依赖关系的解决方式发生了变化。之前需要业务对象自己去拉取所依赖的业务对象;现在有了BeanFactory这类的IoC容器之后,需要依赖什么对象就让BeanFactory为我们推过来就行了,即拥有BeanFactory之后使用IoC模式进行系统业务对象的开发
1. 管理依赖关系
使用IoC模式开发的业务对象不用操心如何解决相互之间的依赖关系,这些事情全部交给BeanFactory来处理即可,BeanFactory通常会通过常用的XML文件来注册并管理各个业务之间的依赖关系
2. 业务对象的使用
BeanFactory之前,通常会在应用程序的入口main方法中,自己实例化相应的对象并调用
使用BeanFactory之后,我们只需要将依赖关系注册到BeanFactory中,就可以让BeanFactory替我们生产一个业务对象
对象注册和依赖绑定
为了明确管理各个业务对象以及业务对象之间的依赖绑定关系,需要通过某种途径记录和管理这些信息
直接编码
BeanFactory、BeanDefinitionRegistory和DefaultListableBeanFactory之间的关系
- BeanFactory只是一个接口,最终需要一个该接口的实现来进行实际的Bean的管理
- DefaultListableBeanFactory就是BeanFactory的一个比较通用的实现类
- 实现了BeanFactory接口,只定义了如何访问容器内管理的Bean的方法
- 实现了BeanDefinitionRegistory接口,负责管理Bean的注册
- BeanFactory只定义如何访问容器内的Bean,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作,BeanDefinitionRegistory接口定义了Bean的注册逻辑,所以,一般BeanFactory的实现类也会实现这个接口来管理Bean的注册
举例:
BeanFactory比作图书馆,BeanDefinitionRegistory相当于图书馆的书架,BeanDefinition就相当于书架上具体的图书
每一个受管的对象,在容器中都有一个BeanDefinition的实例(instance)与之对应,该BeanDefinition实例负责保存对象的所有必要信息(class类型、是否是抽象类、构造方法参数以及其他属性信息等)
当客户端向BeanFactory请求相应对象时,BeanFactory会通过这些信息给客户端返回一个完备可用的对象实例,BeanDefinition常见的两个实现类是RootBeanDefinition和ChildBeanDefinition
配置文件
Spring支持properties和xml两种格式的配置文件格式
采用外部配置文件时,Spring的IoC容器有一个统一的处理方式,根据不同的外部配置文件格式提供相应的BeanDefinitionReader实现类,该实现类主要职责:
- 将相应的配置文件读取并映射到BeanDefinition上
- 解析文件格式
- 装配BeanDefinition
- 将映射后的BeanDefinition注册到一个BeanDefinitionRegistory上,由BeanDefinitionRegistory完成Bean的注册和加载
1. Properties配置文件的加载
Spring提供了PropertiesBeanDefinitionReader类用于加载Properties格式配置文件
Properties配置文件
- djNewsProvider、djListener、djPersister作为beanName,后面通过.(class)表明对应的实现类是什么
- .$[number]表示当前beanName对应的对象需要通过构造方法注入的方式注入相应的依赖对象,number表示参数index,$0表示构造方法第一个参数,$1表示构造方法第二个参数
- (ref)表示所依赖的是引用对象,而不是普通类型,如果不加(ref),djListener和djPersiter将以String类型进行注入
加载过程
所有信息配置在Properties文件中,不需要通过代码进行对象的注册和依赖绑定。这些工作全都交给相应的BeanDefinitionReader实现(PropertiesBeanDefinitionReader)来完成
2.XML格式配置文件的加载
XML配置文件
相比于Properties文件,XML格式内容更容易理解
加载过程
Spring提供了XmlBeanDefinitionReader类加载XML文件
- XmlBeanDefinitionReader读取XML文件并解析
- 将解析后的文件内容映射到相应的BeanDefinition上,并加载到相应的BeanDefinitionRegistory中
此外,Spring还在Default’List’able’BeanFactory的基础上构建了简化XML格式配置加载的XmlBeanFactory实现(上述代码最后一行,实现相当简单)
注解
Spring提供了@Autowired和@Component两个注解对相关类进行标记
(1)注册和绑定
- @Autowired注解告诉Spring容器需要为当前对象注入哪些依赖对象
- @Component注解配合classpath-scanning功能确定那些类需要交给Spring容器管理
(2)使用
XML文件
XML格式的容器信息管理方式是Spring提供的最强大、支持最全面的方式。有必要了解Spring对XML文件配置的定义
beans和bean
所有注册到Spring容器中的业务对象都称之为Bean,所以每一个对象在XML中的映射也自然而然的对应一个叫
注册到容器的所有业务对象都可以被Spring容器管理,所以在XML中把这些叫做
(1)
a、属性
- default-lazy-init,用来标志是否对所有的
进行延迟加载,可选值true/false,默认false - true,延迟加载
- false,默认选项,立即加载
- default-autowire,使用自动绑定时,用来标志全体bean使用哪一种绑定方式
- no,默认选项
- byName
- byType
- constructor
- autodetect
- default-dependency-check
- none,默认选项,不做依赖检查
- objects
- simple
- all
- default-init-method,如果所有
都有同样名称的初始化方法,可以直接在这里统一指定,就不用在每一个 上重复单独指定了 - default-destory-method,如果所有
都有同样名称的销毁方法,可以直接在这里统一指定,就不用在每一个 上重复单独指定了
b、子元素(了解即可)
,指定一些描述性的信息 ,存在多个配置文件,依赖对象定义在另一个配置文件中定义时。比如A.xml中的 定义可能依赖B.xml中的 定义,就可以在A.xml中使用 将B.xml引入到A.xml - 容器实际上可以加载多个配置文件,不需要通过一个配置文件加载所有配置,也不常用
,为某个 起别名
(2)
属性
- id,每个注册到容器的对象的唯一标志,用来指定当前注册对象的beanName,有些情况下可以不指定该属性
- name,别名(alias)
- 可以使用id不能使用的字符,比如/,还可以通过逗号、空格或者冒号分割指定多个name
- class,指定注册到容器的对象类型,大多数情况下该属性是必须的,少数情况下不需要指定(抽象配置模板)
XML
(1)构造方法注入
元素指明容器将为djNewsProvider这个bean注入所引用的bean实例。还可以简写为:
有些时候,容器加载XML配置的时候无法明确配置项和对象的构造方法参数的对应关系,就需要使用到
比如:
- 对象存在多个构造方法,当参数列表数目相同而类型不相同的时候,容器无法区分应该使用哪个构造方法来实例化对象
- 构造方法可能同时传入至少两个类型相同的对象
a、type属性
指定构造方法参数类型
使用示例:
两个构造方法,参数数目一致但是类型不同,进行如下配置
从BeanFactory取得对象调用toString方法打印结果如下:
如何调用参数为int类型的构造方法呢?使用type属性
打印结果
b、index属性
指定构造方法参数索引位置
使用示例:
构造方法的两个参数是相同类型的,如何确定配置中
打印结果
如何让‘22222’作为构造方法的第二个参数传递
1、颠倒配置
2、使用index属性
打印结果
(2)setter方法注入
Spring为setter方法注入提供了
属性(name、ref、value)
通过name属性指定该
注意:只使用
简化上面代码如下