一. 基础概念
1. 什么是IOC
Ioc-----Inversion of control,即“控制反转”,它只是一种设计思想。它意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
(1)谁控制谁
传统的是通过new来创建对象;但是IOC思想是通过一个专门的容器来创建这些对象。即:IOC容器控制了对象。
(2)控制了什么
主要是控制了外部资源的获取(不仅是对象,还包含文件等资源的获取)
(3)为何是反转
因为容器帮我们查找及注入依赖对象,对象只是被动的接收依赖对象
(4)哪些地方反转了
依赖对象的获取被反转了
(5)Spring IOC容器如何知道哪些是它管理的对象呢
这就需要配置文件。容器是通过读取配置文件中的配置元数据来实现管理的。(比如读取bean id)配置文件可以是xml,注解,Java文件等等,这些都是和spring完全解耦的。
(6)Spring IOC容器管理的对象叫什么
Bean
note:在Spring种BeanFactory是IOC容器的实际代表者。
2. 什么是Bean
Bean就是由Spring容器初始化、装配及管理的对象。
Bean定义在容器内部用BeanDefinition对象表示。
3. 什么是DI
DI(Dependency Injection),即“依赖注入”:是组件之间依赖关系由容器来决定添加。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。也可以理解成IOC的别名。
4. 什么是IOC容器
IOC容器就是具有依赖注入功能的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象之间的依赖。应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装。在Spring中IOC容器的实际代表者有BeanFactory和ApplicationContext.
5.Spring IOC容器如何知道哪些是它管理的对象呢?
这就需要配置文件,Spring IoC容器通过读取配置文件中的配置元数 据,通过元数据对应用中的各个对象进行实例化及装配。一般使用基于xml配置文件进行配置元数据,而且Spring与配 置文件完全解耦的,可以使用其他任何可能的方式进行配置元数据,比如注解、基于java文件的、基于属性文件的配置 都可以。
二. IOC容器相关API
在Spring Ioc容器的代表就是org.springframework.beans包中的BeanFactory接口,BeanFactory接口提供了IoC容器 最基本功能;而org.springframework.context包下的ApplicationContext接口扩展了BeanFactory,还提供了与 Spring AOP集成、国际化处理、事件传播及提供不同层次的context实现 (如针对web应用的 WebApplicationContext)。简单说, BeanFactory提供了IoC容器最基本功能,而 ApplicationContext 则增加了更多 支持企业级功能支持。ApplicationContext完全继承BeanFactory,因而BeanFactory所具有的语义也适用于 ApplicationContext。
1. IOC容器的代表者可以分为两类:BeanFactory(基础)+ApplicationContext(高级)
(1)BeanFactory
它是基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需 要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的 IoC容器选择。
(2)ApplicationContext
ApplicationContext所管理 的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来 说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容 器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中, ApplicationContext类型的容器是比较合适的选择。
note: 将应用所需 的所有业务对象交给BeanFactory之后,剩下要做的,就是直接从BeanFactory取得终组装完成并 且可用的对象。至于这个终业务对象如何组装,你不需要关心,BeanFactory会帮你搞定。所以,对于客户端来说,与BeanFactory打交道其实很简单。基本地,BeanFactory肯定会公 开一个取得组装完成的对象的方法接口。
2. 常用容器的实现类
(1)XmlBeanFactory:BeanFactory实现,提供基本的IoC容器功能,可以从classpath或文件系统等获取资源;
File file=new File("fileSystemConfig.xml"); Resource resource=new FileSystemResource(file); BeanFactory beanFactory=new XmlBeanFactory(resource); Resource resource2=new ClassPathResource("classpath.xml"); BeanFactory beanFactory2=new XmlBeanFactory(resource2);
(2)ClassPathXmlApplicationContext:ApplicationContext实现,从classpath获取配置文件;
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath.xml");
(3)FileSystemXmlApplicationContext:ApplicationContext实现,从文件系统获取配置文件。
BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig.xml");
3. ApplicationContext接口获取Bean方法简介
- Object getBean(String name) 根据名称返回一个Bean,客户端需要自己进行类型转换;
- T getBean(String name, Class<T> requiredType) 根据名称和指定的类型返回一个Bean,客户端无需自己进行类型 转换,如果类型转换失败,容器抛出异常;
- T getBean(Class<T> requiredType) 根据指定的类型返回一个Bean,客户端无需自己进行类型转换,如果没有或有 多于一个Bean存在容器将抛出异常;
- Map<String, T> getBeansOfType(Class<T> type) 根据指定的类型返回一个键值为名字和值为Bean对象的 Map, 如果没有Bean对象存在则返回空的Map。
三. BeanFactory的工作原理-----(BeanFactory的对象注册与依赖绑定方式)
通常情况下,BeanFactory会通过常用的XML文件来注册并管理各个业务对象之间的依赖关系。通常会有3种方式来管理这些关系。将应用所需要的所有业务对象交给BeanFactory之后,剩下要做的,就是直接从BeanFactory取得最终组装完成并且可用的对象。
业务背景:假设有一个FX(外汇贸易)项目,经常需要近乎实时地为客户提供外汇新闻。通常情况下,都是先从不同的新闻社订阅新闻来源,然后通过批处理程序定时地到指定的新闻服务器抓取最新的外汇新闻,接着将这些新闻存入本地数据库,最后在FX系统的前台界面显示。
假设我们有一个FXNewsProvider类来做以上的工作:
public class FXNewsProvider { private IFXNewsListener newsListener; private IFXNewsPersister newPersister; public FXNewsProvider() { newsListener=new DowJonesNewsListener(); newPersister=new DowJonesNewsPersister(); } public void getAndPersistNews() {}//用来不定时获取且存储获取的数据操作 }
1. 直接编码方式
其实,把编码方式单独提出来称作一种方式并不十分恰当。因为不管什么方式,终都需要编码 才能“落实”所有信息并付诸使用。不过,通过这些代码,起码可以让我们更加清楚BeanFactory在 底层是如何运作的。
BeanFactory 只是一个接口,我们终需要一个该接口的实现来进行实际的Bean的管理,DefaultListableBeanFactory就是这么一个比较通用的BeanFactory实现类。DefaultListableBean Factory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才 是在BeanFactory的实现中担当Bean注册管理的角色。基本上,BeanFactory接口只定义如何访问容 器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。 BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现 类会实现这个接口来管理Bean的注册。
打个比方说,BeanDefinitionRegistry就像图书馆的书架,所有的书是放在书架上的。虽然你 还书或者借书都是跟图书馆(也就是BeanFactory,或许BookFactory可能更好些)打交道,但书架才 是图书馆存放各类图书的地方。所以,书架相对于图书馆来说,就是它的“BookDefinitionRegistry”
每一个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之相对应,该 BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象 类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会 通过这些信息为客户端返回一个完备可用的对象实例。RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类。
分析上面的代码:
(1)在 main 方法中,首先构造一个DefaultListableBeanFactory 作为BeanDefinitionRegistry,然后将其交给bindViaCode方法进行具体的对象注册和相关依赖管理,然后通过 bindViaCode返回的BeanFactory取得需要的对象,后执行相应逻辑。在我们的实例里,当 然就是取得FXNewsProvider进行新闻的处理。
(2)在bindViaCode方法中,首先针对相应的业务对象构造与其相对应的BeanDefinition,使用 了 RootBeanDefinition 作为BeanDefinition 的实现类。构造完成后,将这些 BeanDefinition注册到通过方法参数传进来的BeanDefinitionRegistry中。之后,因为我 们的FXNewsProvider是采用的构造方法注入,所以,需要通过ConstructorArgumentValues为其注入相关依赖。在这里为了同时说明setter方法注入,也同时展示了在Spring中如 何使用代码实现setter方法注入。如果要运行这段代码,需要把setter方法注入部分的4行代码注 释掉。后,以BeanFactory的形式返回已经注册并绑定了所有相关业务对象的BeanDefini- tionRegistry实例.
我的总结:通过Java代码的方式实现BeanFactory的工作原理过程:1)首先需要为各个对象创建对应的BeanDefinition。2)将这些BeanDefinition注册到BeanDefinitionRegistry中去;3)然后通过构造器注入或者setter方法注入的方式给客户端请求的对象中进行依赖注入;4)通过BeanFactory返回客户端请求的对象
2. 外部配置文件方式
Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。当然,如果你愿 意也可以引入自己的文件格式,前提是真的需要。
采用外部配置文件时,Spring的IoC容器有一个统一的处理方式。通常情况下,需要根据不同的外 部配置文件格式,给出相应的BeanDefinitionReader实现类,由BeanDefinitionReader的相应实 现类负责将相应的配置文件内容读取并映射到BeanDefinition,然后将映射后的BeanDefinition注 册到一个BeanDefinitionRegistry,之后,BeanDefinitionRegistry即完成Bean的注册和加载。 当然,大部分工作,包括解析文件格式、装配BeanDefinition之类的工作,都是由BeanDefinition- Reader的相应实现类来做的,BeanDefinitionRegistry只不过负责保管而已。整个过程类似于如下 代码:
BeanDefinitionRegistry beanRegistry = <某个BeanDefinitionRegistry实现类,通常为DefaultListableBeanFactory>; BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);
beanDefinitionReader.loadBeanDefinitions("配置文件路径"); // 现在我们就取得了一个可用的BeanDefinitionRegistry
(1)Properties配置格式的加载
Spring提供了org.springframework.beans.factory.support.PropertiesBeanDefinition- Reader类用于Properties格式配置文件的加载,所以,我们不用自己去实现BeanDefinitionReader, 只要根据该类的读取规则,提供相应的配置文件即可。
对于FXNews系统的业务对象,我们采用如下文件内容(见代码清单4-5)进行配置加载。
说明上面的配置含义:
- djNewsProvider作为beanName,后面通过.(class)表明对应的实现类是什么;
- 通过在表示beanName的名称后添加.$[number]后缀的形式,来表示当前beanName对应的对 象需要通过构造方法注入的方式注入相应依赖对象。在这里,我们分别将构造方法的第一个 参数和第二个参数对应到djListener和djPersister。需要注意的一点,就是$0和$1后面的 (ref),(ref)用来表示所依赖的是引用对象,而不是普通的类型。如果不加(ref), PropertiesBeanDefinitionReader会将djListener和djPersister作为简单的String类型 进行注入,异常自然不可避免啦。
- FXNewsProvider采用的是构造方法注入,而为了演示setter方法注入在Properties配置文件中又 是一个什么样子,以便于你更全面地了解基于Properties文件的配置方式,我们在下面增加了 setter方法注入的例子,不过进行了注释。实际上,与构造方法注入大的区别就是,它不使 用数字顺序来指定注入的位置,而使用相应的属性名称来指定注入。newsListener和 newPersistener恰好就是我们的FXNewsProvider类中所声明的属性名称。这印证了之前在 比较构造方法注入和setter方法注入方式不同时提到的差异,即构造方法注入无法通过参数名 称来标识注入的确切位置,而setter方法注入则可以通过属性名称来明确标识注入。与在 Properties中表达构造方法注入一样,同样需要注意,如果属性名称所依赖的是引用对象,那 么一定不要忘了(ref)。
当这些对象之间的注册和依赖注入信息都表达清楚之后,就可以将其加载到BeanFactory而付诸 使用了。而这个加载过程实际上也就像我们之前总体上所阐述的那样,代码清单4-6中的内容再次演示 了类似的加载过程。
note: Spring提供的PropertiesBeanDefinitionReader是按照Spring自己的文件配置规则进行加载的,我们也可以自己定义规则,且改变Reader的实现方式。
(2)XML配置格式的加载
XML配置格式是Spring支持完整,功能强大的表达方式。当然,一方面这得益于XML良好的 语意表达能力;另一方面,就是Spring框架从开始就自始至终保持XML配置加载的统一性。同Properties 配置加载类似,现在只不过是转而使用XML而已。Spring 2.x之前,XML配置文件采用DTD(Document Type Definition)实现文档的格式约束。2.x之后,引入了基于XSD(XML Schema Definition)的约束 方式。不过,原来的基于DTD的方式依然有效,因为从DTD转向XSD只是“形式”上的转变,所以, 后面的大部分讲解还会沿用DTD的方式,只有必要时才会给出特殊说明。
有了XML配置文件,我们需要将其内容加载到相应的BeanFactory实现中,以供使用,如下代码所示:
代码说明:
与为Properties配置文件格式提供PropertiesBeanDefinitionReader相对应,Spring同样为XML 格式的配置文件提供了现成的BeanDefinitionReader实现,即XmlBeanDefinitionReader。 XmlBeanDefinitionReader负责读取Spring指定格式的XML配置文件并解析,之后将解析后的文件内 容映射到相应的BeanDefinition,并加载到相应的BeanDefinitionRegistry中(在这里是Default- ListableBeanFactory)。这时,整个BeanFactory就可以放给客户端使用了。
3. 注解方式
- 在Spring 2.5发布之前,Spring框架并没有正式支持基于注解方式的依赖注入;
- Spring 2.5发布的基于注解的依赖注入方式,如果不使用classpath-scanning功能的话,仍然部分 依赖于“基于XML配置文件”的依赖注入方式。
另外,注解是Java 5之后才引入的,所以,以下内容只适用于应用程序使用了Spring 2.5以及Java 5 或者更高版本的情况之下。
如果要通过注解标注的方式为FXNewsProvider注入所需要的依赖,现在可以使用@Autowired以 及@Component对相关类进行标记。代码清单4-9演示了FXNews相关类使用指定注解标注后的情况。
说明:@Autowired是这里的主角,它的存在将告知Spring容器需要为当前对象注入哪些依赖对象。而 @Component则是配合Spring 2.5中新的classpath-scanning功能使用的。现在我们只要再向Spring的配置 文件中增加一个“触发器”,使用@Autowired和@Component标注的类就能获得依赖对象的注入了。
<context:component-scan/>会到指定的包(package)下面扫描标注有@Component的类,如果 找到,则将它们添加到容器进行管理,并根据它们所标注的@Autowired为这些类注入符合条件的依 赖对象。
在以上所有这些工作都完成之后,我们就可以像通常那样加载配置并执行当前应用程序了,如以 下代码所示:
以上就是一些关于IOC的基本原理以及IOC容器的工作原理。接下来会具体进行IOC使用的的学习。
参考文献:
http://jinnianshilongnian.iteye.com
《spring揭秘》