学习《spring揭秘》心得
1,Ioc是什么-Inversion of Control
Ioc的理念:让别人为你服务。从事必躬亲到让别人为你服务
Ioc是解决之道。用什么让别人直接送过来。这个别人可以称作。IoC Service Provider。我们用图来表示这种关系
通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC 的场景中,二者之间通过IoC Service Provider来打交道,所有的被注入对象和依赖对象现在由IoC Service Provider统一管理。被注入对象需要什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider 为被注入对象服务的目的。IoC Service Provider在这里就是通常的IoC 容器所充当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了IoC Service Provider那里
2,IoC Service Provider提供服务的途径(依赖注入的方式)
IoC 模式最权威的总结和解释,应该是Martin Fowler的那篇文章“Inversion of Control Containers and the DependencyInjection pattern ”,其中提到了三种依赖注入的方式,即构造方法注入(constructor injection )、setter方法注入(setter injection )以及接口注入(interface injection )。
三种方式的说明:
a, 构造方法注入:被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC 容器)知道它需要哪些依赖对象。IoC Service Provider 会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。这就好比你刚进酒吧的门,服务生已经将你喜欢的啤酒摆上了桌面一样。坐下就可马上享受一份清凉与惬意。
b, setter 方法注入
对于JavaBean 对象来说,通常会通过setXXX()和getXXX()方法来访问对应属性。这些setXXX()方法统称为setter方法,getXXX()当然就称为getter方法。通过setter方法,可以更改相应的对象属性,通过getter方法,可以获得相应属性的状态。所以,当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。
setter方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些,可以在对象构造完成后再注入。这就好比你可以到酒吧坐下后再决定要点什么啤酒,可以要百威,也可以要大雪,随意性比较强。如果你不急着喝,这种方式当然是最适合你的。
c,接口注入
被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC ServiceProvider 最终通过这些接口来了解应该为被注入对象注入什么依赖对象。
相对于前两种依赖注入方式,接口注入比较死板和烦琐。如果需要注入依赖对象,被注入对象就必须声明和实现另外的接口。这就好像你同样在酒吧点啤酒,为了让服务生理解你的意思,你就必须戴上一顶啤酒杯式的帽子。
三种注入方式的比较
a, 接口注入。从注入方式的使用上来说,接口注入是现在不甚提倡的一种方式,基本处于“退役状态”。因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter方法注入则不需要如此。
b,构造方法注入。这种注入方式的优点就是,对象在构造完成之后,即已进入就绪状态,可以马上使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且在Java 中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。
c,setter 方法注入。因为方 法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些。另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE 支持。缺点当然就是对象无法在构造完成后马上进入就绪状态。
综上所述,构造方法注入和 setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式;而接口注入因为侵入性较强,近年来已经不流行了
3,IoC Service Provider职责介绍:
职责:业务对象的构建管理和业务对象间的依赖绑定
a,业务对象的构建管理。在 IoC 场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以,IoC Service Provider 需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
b,业务对象间的依赖绑定。对于IoC Service Provider来说,这个职责是最艰巨也是最重要的,这是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointerException)。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。
4,IoC ServiceProvider 产品使用的注册对象管理信息的方式
直接编码方式
配置文件方式(spring当中经常用的,也是我们目前在用的。)
元数据方式(这里指注解)
5,Spring 的IoC 容器
Spring 的IoC 容器是一个IoC Service Provider ,但是,这只是它被冠以IoC 之名的部分原因,我们不能忽略的是“容器”。Spring 的IoC 容器是一个提供IoC 支持的轻量级容器,除了基本的IoC 支持,它作为轻量级容器还提供了IoC 之外的支持。如图:
5,1:Spring 提供的两种容器类型:BeanFactory和ApplicationContext。
1,BeanFactory。基础类型IoC 容器,提供完整的IoC 服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load )。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC 容器选择。
2,ApplicationContext。ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择。BeanFactory和ApplicationContext继承关系如图:
5,2:BeanFactory的对象注册与依赖绑定方式。
a,直接编码方式,代码段:
public staticBeanFactory codeDemo(){
DefaultListableBeanFactorybeanRegistry = newDefaultListableBeanFactory();
BeanFactorybeanFactory = bindCode(beanRegistry);
// Personp = (Person) factory.getBean("person");
// p.sayHello();
return beanFactory;
}
@SuppressWarnings("deprecation")
private static BeanFactory bindCode(
DefaultListableBeanFactorybeanRegistry) {
//构建BeanDefinition
AbstractBeanDefinitionperson = newRootBeanDefinition(Person.class,true);
AbstractBeanDefinitionsayHello = newRootBeanDefinition(EnglishSayHello.class,true);
//BeanDefinition register to BeanRegistry
beanRegistry.registerBeanDefinition("person",person);
beanRegistry.registerBeanDefinition("sayHello",sayHello);
//绑定(构造器)
// ConstructorArgumentValuesconstructor = new ConstructorArgumentValues();
// constructor.addIndexedArgumentValue(0,sayHello);
// person.setConstructorArgumentValues(constructor);
//绑定(set。。。)
MutablePropertyValues propertyValues = newMutablePropertyValues();
propertyValues.addPropertyValue(new PropertyValue("sayHello",sayHello));
person.setPropertyValues(propertyValues);
return(BeanFactory)beanRegistry;
}
通过代码可以看出来。基本上,BeanFactory接口只定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现类会实现这个接口来管理Bean的注册。关系如图
b, 外部配置文件方式,(properties,xml)
采用外部配置文件时,Spring 的IoC 容器有一个统一的处理方式。通常情况下,需要根据不同的外部配置文件格式,给出相应的BeanDefinitionReader实现类,由BeanDefinitionReader的相应实现类负责将相应的配置文件内容读取并映射到BeanDefinition ,然后将映射后的 BeanDefinition 注册到一个BeanDefinitionRegistry,之后,BeanDefinitionRegistry即完成Bean的注册和加载。当然,大部分工作,包括解析文件格式、装配BeanDefinition 之类的工作,都是由BeanDefinition- Reader的相应实现类来做的,BeanDefinitionRegistry只不过负责保管而已。整个过程类似于如下代码(伪代码):
以xml为例:使用xml方式的代码:
- DefaultListableBeanFactorybeanRegistry = newDefaultListableBeanFactory();
- XmlBeanDefinitionReaderreader = newXmlBeanDefinitionReader(beanRegistry);
- reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
- (Person)(beanRegistry.getBean("person"))).sayHello();
除了提供XmlBeanDefinitionReader用于XML格式配置文件的加载,Spring 还在Default-
ListableBeanFactory的基础上构建了简化XML格式配置加载的XmlBeanFactory 实现。
如下:
XmlBeanFactoryfactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Person p =(Person) factory.getBean("person");
可以看到使用了XmlBeanFactory 之后,完成XML的加载和BeanFactory的初始化是多么简单。
C,注解方式
5,3:Spring 的IoC 容器内部实现,
Sping官方的参考手册有张图反应了spring所起的作用
它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。
Spring 的IoC 容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean实例化阶段。
Spring 的IoC 容器在实现的时候,充分运用了这两个实现阶段的不同特点,在每个阶段都加入了相应的容器扩展点,以便我们可以根据具体场景的需要加入自定义的扩展逻辑。如图:
1. 容器启动阶段
容器启动伊始,首先会通过某种途径加载Configuration MetaData 。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition ,最后把这些保存了bean 定义必要信息的BeanDefinition ,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。如图所示:
总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然,一些验证性或者辅助性的工作也可以在这个阶段完成。
2. Bean实例化阶段
经过第一阶段,现在所有的bean 定义信息都通过BeanDefinition 的方式注册到了BeanDefini- ionRegistry 中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。 该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的beanDefinition 所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。
如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。
5,4:扩展spring
a,插手“容器的启动”。
Spring 提供了一种叫做BeanFactoryPostProcessor 的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition 所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition 做一些额外的操作,比如修改其中bean 定义的某些属性,为bean 定义增加其他信息等。可以自定义实现BeanFactoryPostProcessor ,实现org.springframework.beans.factory.config.BeanFactoryPostProcessor接口。同时,因为一个容器可能拥有多个BeanFactoryPostProcessor,这个时候可能需要实现类同时实现org.springframework.core. Ordered接口,以保证各个BeanFactoryPostProcessor 可以按照预先设定的顺序执行(如果顺序紧要的话)。因为Spring 已经提供了几个现成的BeanFactoryPostProcessor 实现类,大多时候,我们很少自己实现BeanFactoryPostProcessor其中,PropertyPlaceholderConfigurer 和PropertyOverrideConfigurer是两个比较常用的BeanFactoryPostProcessor 。
另外,为了处理配置文件中的数据类型与真正的业务对象所定义的数据类型转换,Spring 还允许我们通过CustomEditorConfigurer 来注册自定义的PropertyEditor以补助容器中默认的PropertyEditor,CustomEditorConfigurer是另一种类型的BeanFactoryPostProcessor 实现,它只是辅助性地将后期会用到的信息注册到容器,BeanDefinition 没有做任何变动。
我们知道,不管对象是什么类型,也不管这些对象所声明的依赖对象是什么类型,通常都是通过XML(或者properties 甚至其他媒介)文件格式来配置这些对象类型。但XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而CustomEditorConfigurer 就是帮助我们传达类似信息的。Spring 框架还提供了自身实现的一PropertyEditor,这些PropertyEditor 大部分都位于org.springframework.beans.propertyeditors 包下。
通过以上部分我们知道了spring在容器启动阶段给我们提供的扩展点:org.springframework.beans.factory.config. BeanFactoryPostProcessor你需要插手spring的启动的话,去试试吧。
以上是本人学习《spring揭秘》的一些小小心得。因为水平有限,能力一般。欢迎大家,吐槽,拍砖。

本文深入探讨Spring IoC容器的核心概念,包括Ioc理念、IoCServiceProvider职责、依赖注入方式、Spring容器类型及使用方式。详细阐述了BeanFactory与ApplicationContext的区别、直接编码、配置文件、注解方式的注册与依赖绑定过程。最后介绍了如何扩展Spring容器,包括插入手动配置和使用注解等方法。
1735

被折叠的 条评论
为什么被折叠?



