IoC容器系列的设计与实现
概要
博客介绍了BeanFactory这个容器的基本接口,如getBean(String name),containsBean(String name)等等,但是这些接口并没有具体的实现,因此给出了XmlBeanFactory容器的实现过程,还用编程的方式使用了DefaultListableBeanFactory这个容器,成功得获得了在BeanDefinition中定义的一个Bean.通过编程式的使用,让我们明白IoC容器中的关键的类之间的相互依赖关系,如Resource,BeanDefinitionReader等。
//Spring中BeanDefinition都要封装成Resource的格式才能进行处理,因为beans.xml在类路径下,所以用ClassPathResource
ClassPathResource res =new ClassPathResource("beans.xml");
DefalutListableBeanFactory factory =new DefaultListableBeanFactory();
//对XML文件的处理其实是通过XmlBeanDefinitionReader来处理的(详见BeanFactory容器的设计原理),然后可以理解需要将得到的Bean定义信息传入factory工厂中,让它生成对象,所以需要传入一个factory对象。
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
//让res进行载入
read.loadBeanDefinitions(res);
//其实这样就完成了容器初始化的过程,后面就可以通过getBean(String name )得到相应的Bean了。大家可以试试~~
BeanFactory的应用场景
正如上一篇所提到的,BeanFactory提供的是最基本的IoC容器的功能。BeanFactory接口定义了IoC容器最基本的形式。很显然,Spring并没有给出容器的具体实现。下面来看看Spring是怎样定义IoC容器的基本接口的。
首先最基本的是设计了getBean方法,这个方法是使用IoC容器API的主要方法。通过这个方法,可以取得这个容器管理的Bean(通过指定名字来索引),取得了Bean之后,就可以应用在程序之中。
BeanFactory接口:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
//很方便的通过各种不同方法获取容器中的Bean
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
//判断容器中是否包含该名字的Bean
boolean containsBean(String name);
/*判断Bean的作用域:
prototype :该作用域将单一 bean 的定义限制在任意数量的对象实例
singleton : 该作用域将 bean 的定义的限制在每一个 Spring IoC 容器中的一个单一实例(默认)。
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
//判断该名字对应的Bean的Class类型是否是特定的Class类型。这个Class类型可以由用户来指定
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
//得到该Bean的Class类型
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
//查询该Bean的所有别名。这些别名都是在BeanDefinition中定义的
String[] getAliases(String name);
BeanFactory容器的设计原理
BeanDefinition提供了使用IoC容器的规范,在这个基础上,Spring还提供了符合这个IoC容器接口的一系列的容器的实现让我们使用。以XmlBeanFactory的实现为例:
可以看到,XmlBeanFactory不像之前的ApplicationContext,后者继承了各式各样的接口,有着丰富的功能(上一篇有说明),XmlBeanFactory只提供了最基本的IoC容器的功能。我们可以认为:直接的BeanFactory实现是IoC容器的基本形式,比如这个XmlBeanFactory.而各种ApplicationContext的实现则是IoC容器的高级表现形式。 下面,我们就从XmlBeanFactory的实现入手分析,看看一个基本的IoC容器是怎样实现的。
XmlBeanFactory继承自DefaultListableBeanFactory这个类,后者非常重要,是我们经常要用到的一个IoC容器的实现,在设计ApplicationContext的时候也有用到它。DefaultListableBeanFactory实际上已经包含了基本IoC容器所具有的重要功能。在Spring中实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IoC容器来使用的。而XmlBeanFactory继承了它之后,又增加了新的功能:它是一个可以读取以XML文件定义BeanDefinition的IoC容器。XmlBeanFactory又是怎样实现读取XML文件的呢?
1. 其实,对这些XML文件的处理并不是由XmlBeanFactory直接完成的,而是它里面初始化的一个XmlBeanDefinitionReader对象。所以实际上是由XmlBeanDefinitionReader来完成XML文件处理的。
2. 在构建XmlBeanFactory这个IoC容器时,需要指定BeanDefinition的信息来源,而且这个来源需要封装成Spring中的Resource类来给出。(Resource是Spring用来封装I/O操作的类)。比如BeanDefinition是以XML文件的形式,那么可以使用像:ClassPath-Resource res =new ClassPathResource(“beans.xml”) 这样具体的ClassPathResource来构造Resource。
3. 将前面构造的Resource作为构造参数传递给XmlBeanFactory构造函数。这样IoC容器就可以定位到这个BeanDefinition,从而对Bean完成容器的初始化和依赖注入过程。
public class XmlBeanFactory extends DefaultListableBeanFactory {
//初始化XmlBeanDefinitionReader对象
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
//从BeanDefinition资源文件中载入信息,loadBeanDefinitions也是IoC容器初始化的重要组成部分
this.reader.loadBeanDefinitions(resource);
}
可以看到,XmlBeanFactory继承了DefaultListableBeanFactory,后者的重要性不多说~(已经写了好几遍了)参考XmlBeanFactory的实现过程,我们用编程的方式使用DefaultListableBeanFactory,因为这个编程式使用DefaultListableBeanFactory的过程,可以让我们明白IoC容器中的关键的类之间的相互依赖关系,如Resource,BeanDefinitionReader等等。
ClassPathResource res =new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory =new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinition(res);
//假如BeanDefinition中有一个name为helloWorld的Bean
HelloWorld hw5 = (HelloWorld) factory.getBean("helloWorld");
System.out.println("5=" + hw5.getMessage());
beans.xml中的BeanDefinition定义:
<bean id="helloWorld" class="com.lumingfeng.springStudy.helloSpring.HelloWorld" scope="singleton" init-method="init" destroy-method="destroy" >
<property name="message" value="hello,spring">
</property>
</bean>
HelloWorld Bean类:
package com.lumingfeng.springStudy.helloSpring;
public class HelloWorld {
private String message;
private String address;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void init(){
System.out.println("被初始化");
this.address="123";
}
public String getAddress(){
return address;
}
public void destroy(){
System.out.println("被销毁");
this.address="456";
}
}
输出的结果:
5=hello,spring
这样,我们就可以通过factory对象来使用DefaultListableBeanFactory这个IoC容器了。这个过程和我们上面提到的XmlBeanFactory读取XML文件的步骤是基本一致的:
1. 创建IoC配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息(ClassPathResource res =new ClassPathResource(“beans.xml”);)
2. 创建一个BeanFactory,这里使用了DefaultListableBeanFactory(DefaultListableBeanFactory factory =new DefaultListableBeanFactory();)
3. 创建一个载入BeanDefiniton的读取器–BeanDefinitionReader对象,这里使用XmlBeandefinitionReader来载入XML文件形式的(XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);)BeanDefinition,然后通过一个回调配置给BeanFactory。
4. 从定义的资源文件位置读取配置信息,具体这个解析过程由这里使用XmlBeandefinitionReader来完成(reader.loadBeanDefinition(res);)
完成整个载入和注册Bean定义之后,需要的IoC容器就建立起来了。这个时候就可以直接使用IoC容器了
ApplicationContext的应用场景
上一节中我们了解了IoC容器建立的基本步骤,可以很方便的通过编程的方式来手动控制这些配置和容器的建立过程。但是在Spring中系统为我们提供了许多已经定义好的容器实现,如ApplicationContext.
如图,可以看到ApplicationContext在BeanFactory的基础上添加了附加功能
1. 支持不同的信息源.通过扩展了MessageSource接口,这些信息源的扩展功能可以支持国际化的实现,为开发多语言版本提供支持
2. 访问资源.这一特性体现在对ResourceLoader和Resource的支持上,这样我们可以方便的从不同地方得到Bean定义资源,尤其是从不同的I/O途径。
3. 支持应用事件,继承了接口ApplicationEventPublisher,从而在上下文中引入了事件机制。这些事件和Bean的生命周期的结合为Bean的管理提供了便利。
ApplicationContext的设计原理
我们以常用的FileSytemXmlApplicationContext的实现为例来说明ApplicationContext容器的设计原理.
在FileSystemXmlApplicationContext的设计中可以看到,可以看到ApplicationContext的主要功能已经在它的基类AbstractXmlApplicationContext中实现了。在FileSystemXmlApplicationContext中,只需要实现和它自身设计相关的两个功能:
1. 如果直接使用FileSystemXmlApplicationContext,对于实例化这个ApplicationContext的支持,同时启动IoC容器的refresh()过程。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
refresh()过程会涉及到IoC容器启动的一系列复杂操作,对于不同的容器实现,这些操作都是类似的,所以在基类中将它们封装好。所以我们在FileSystemXml的设计中看到的只是一个简单的调用。
2. 另一个功能是FileSystemXmlApplicationContext设计具体相关的功能,这部分与怎样从文件系统中加载XML的Bean定义资源有关。
通过这个过程,可以为在文件系统中读取以XML形式存在的BeanDefinition做准备,因为不同的ApplicationContext对应着不同的读取BeanDefinition的方式,在FileSystemXmlApplicationContext中实现的代码如下:
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
调用这个方法,就可以得到FileSystemResource的资源定位
IoC容器初始化的过程
看完再做总结~~每篇文章短点为好,太长的让人看了失去耐性,能用短的篇幅把一个知识点说明白也是可以的~**欢迎大家一起讨论学习哟**