2.3 IoC容器的初始化过程
简单来说,IoC容器的初始化是由前面介绍的refresh()方法来启动的,这个方法标志着IoC容器的正式启动。具体来说,这个启动包括BeanDefinition的Resouce定位、载入和注册三个基本过程。如果我们了解如何编程式地使用IoC容器,就可以清楚地看到Resource定位和载入过程的接口调用。在下面的内容里,我们将会详细分析这三个过程的实现。
在分析之前,要提醒读者注意的是,Spring把这三个过程分开,并使用不同的模块来完成,如使用相应的ResourceLoader、BeanDefinitionReader等模块,通过这样的设计方式, 可以让用户更加灵活地对这三个过程进行剪裁或扩展,定义出最适合自己的IoC容器的初始化过程。
第一个过程是Resource定位过程。这个Resource定位指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口。对于这些BeanDefinition的存在形式,相信大家都不会感到陌生。比如,在文件系统中的Bean定义信息可以使用FileSystemResource来进行抽象;在类路径中的Bean定义信息可以使用前面提到的ClassPathResource来使用,等等。这个定位过程类似于容器寻找数据的过程,就像用水桶装水先要把水找到一样。
第二个过程是BeanDefinition的载入。这个载入过程是把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。下面介绍这个数据结构的详细定义。具体来说,这个BeanDefinition实际上就是POJO对象在IoC容器中的抽象,通过这个BeanDefinition定义的数据结构,使IoC容器能够方便地对POJO对象也就是Bean进行管理。在下面的章节中,我们会对这个载入的过程进行详细的分析,使大家对整个过程有比较清楚的了解。
第三个过程是向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的。这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。通过分析,我们可以看到,在IoC容器内部将BeanDefinition注入到一个HashMap中去,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。
值得注意的是,这里谈的是IoC容器初始化过程,在这个过程中,一般不包含Bean依赖注入的实现。在Spring IoC的设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。但有一个例外值得注意,在使用IoC容器时有一个预实例化的配置,通过这个预实例化的配置(具体来说,可以通过为Bean定义信息中的lazyinit属性),用户可以对容器初始化过程作一个微小的控制,从而改变这个被设置了lazyinit属性的Bean的依赖注入过程。举例来说,如果我们对某个Bean设置了lazyinit属性,那么这个Bean的依赖注入在IoC容器初始化时就预先完成了,而不需要等到整个初始化完成以后,第一次使用getBean时才会触发。
了解了IoC容器进行初始化的大致轮廓之后,下面我们详细地介绍在IoC容器的初始化过程中,BeanDefinition的资源定位、载入和解析过程是怎么实现的。
2.3.1 BeanDefinition的Resource定位
以编程的方式使用DefaultListableBeanFactory时,首先定义一个Resource来定位容器使用的BeanDefinition。这时使用的是ClassPathResource,这意味着Spring会在类路径中去寻找以文件形式存在的BeanDefinition信息。
ClassPathResource res = new ClassPathResource("beans.xml");
这里定义的Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。在这里,我们也可以看到使用Application-Context相对于直接使用DefaultListableBeanFactory的好处。因为在ApplicationContext中,Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IoC容器,需要为它配置特定的读取器才能完成这些功能。当然,有利就有弊,使用DefaultListableBeanFactory这种更底层的容器,能提高定制IoC容器的灵活性。
回到我们经常使用的ApplicationContext上来,例如FileSystemXmlApplicationContext、ClassPathXmlApplicationContext以及XmlWebApplicationContext等。简单地从这些类的名字上分析,可以清楚地看到它们可以提供哪些不同的Resource读入功能,比如FileSystemXmlApplicationContext可以从文件系统载入Resource,ClassPathXmlApplication-Context可以从Class Path载入Resource,XmlWebApplicationContext可以在Web容器中载入Resource,等等。
下面以FileSystemXmlApplicationContext为例,通过分析这个ApplicationContext的实现来看看它是怎样完成这个Resource定位过程的。作为辅助,我们可以在图2-5中看到相应的ApplicationContext继承体系。


从源代码实现的角度,我们可以近距离关心以FileSystemXmlApplicationConext为核心的继承体系,如图2-6所示。


从图2-6中可以看到,这个FileSystemXmlApplicationContext已经通过继承Abstract-ApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力,因为AbstractApplicationContext的基类是DefaultResourceLoader。下面让我们看看FileSystemXmlApplicationContext的具体实现,如代码清单2-4所示。
代码清单2-4 FileSystemXmlApplicationContext的实现

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {public FileSystemXmlApplicationContext() {}public FileSystemXmlApplicationContext(ApplicationContext parent) {super(parent);}//这个构造函数的configLocation包含的是BeanDefinition所在的文件路径public FileSystemXmlApplicationContext(String configLocation) throws BeansException {this(new String[] {configLocation}, true, null);}//这个构造函数允许configLocation包含多个BeanDefinition的文件路径public FileSystemXmlApplicationContext(String[] configLocations) throws BeansException {this(configLocations, true, null);}//这个构造函数在允许configLocation包含多个BeanDefinition的文件路径的同时,还允许指定//自己的双亲IoC容器public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent)throws BeansException {this(configLocations, true, parent);}public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh)throws BeansException {this(configLocations, refresh, null);}//在对象的初始化过程中,调用refresh函数载入BeanDefinition,这个refresh启动了//BeanDefinition的载入过程,我们会在下面进行详细分析
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh,ApplicationContext parent)throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}}//这是应用于文件系统中Resource的实现,通过构造一个FileSystemResource来得到一个在文件//系统中定位的BeanDefinition//这个getResourceByPath是在BeanDefinitionReader的loadBeanDefintion中被调用的//loadBeanDefintion采用了模板模式,具体的定位实现实际上是由各个子类来完成的
protected Resource getResourceByPath(String path) {if (path != null && path.startsWith("/")) {path = path.substring(1);}return new FileSystemResource(path);}
}

在FileSystemApplicationContext中,我们可以看到在构造函数中,实现了对configuration进行处理的功能,让所有配置在文件系统中的,以XML文件方式存在的BeanDefnition都能够得到有效的处理,比如,实现了getResourceByPath方法,这个方法是一个模板方法,是为读取Resource服务的。对于IoC容器功能的实现,这里没有涉及,因为它继承了AbstractXmlApplicationContext,关于IoC容器功能相关的实现,都是在FileSystemXmlApplicationContext中完成的,但是在构造函数中通过refresh来启动IoC容器的初始化,这个refresh方法非常重要,也是我们以后分析容器初始化过程实现的一个重要入口。
注意 FileSystemApplicationContext是一个支持XML定义BeanDefinition的ApplicationContext,并且可以指定以文件形式的BeanDefinition的读入,这些文件可以使用文件路径和URL定义来表示。在测试环境和独立应用环境中,这个ApplicationContext是非常有用的。
根据图2-7的调用关系分析,我们可以清楚地看到整个BeanDefinition资源定位的过程。这个对BeanDefinition资源定位的过程,最初是由refresh来触发的,这个refresh的调用是在FileSystemXmlBeanFactory的构造函数中启动的,大致的调用过程如图2-8所示。
从Spring源代码实现的角度,我们可以通过Eclipse的功能,查看详细的方法调用栈,如图2-7所示。
大家看了上面的调用过程可能会比较好奇,这个FileSystemXmlApplicationContext在什么地方定义了BeanDefinition的读入器BeanDefinitionReader,从而完成BeanDefinition信息的读入呢 ?在前面分析过,在IoC容器的初始化过程中,BeanDefinition资源的定位、读入和注册过程是分开进行的,这也是解耦的一个体现。关于这个读入器的配置,可以到FileSystemXmlApplicationContext的基类AbstractRefreshableApplicationContext中看看它是怎样实现的。


我们重点看看AbstractRefreshableApplicationContext的refreshBeanFactory方法的实现,这个refreshBeanFactory被FileSystemXmlApplicationContext构造函数中的refresh调用。在这个方法中,通过createBeanFactroy构建了一个IoC容器供ApplicationContext使用。这个IoC容器就是我们前面提到过的DefaultListableBeanFactory,同时,它启动了loadBeanDefinitions来载入BeanDefinition,这个过程和前面以编程式的方法来使用IoC容器(XmlBeanFactory)的过程非常类似。
从代码清单2-4中可以看到,在初始化FileSystmXmlApplicationContext的过程中,通过IoC容器的初始化的refresh来启动整个调用,使用的IoC容器是DefultListableBeanFactory。具体的资源载入在XmlBeanDefinitionReader读入BeanDefinition时完成,在XmlBeanDefinitionReader的基类AbstractBeanDefinitionReader中可以看到这个载入过程的具体实现。对载入过程的启动,可以在AbstractRefreshableApplicationContext的loadBeanDefinitions方法中看到,如代码清单2-5所示。
代码清单2-5 AbstractRefreshableApplicationContext对容器的初始化

protected final void refreshBeanFactory() throws BeansException {//这里判断,如果已经建立了BeanFactory,则销毁并关闭该BeanFactoryif (hasBeanFactory()) {destroyBeans();closeBeanFactory();}//这里是创建并设置持有的DefaultListableBeanFactor的地方同时调用//loadBeanDefinitions再载入BeanDefinition的信息try {DefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);loadBeanDefinitions(beanFactory);            synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing XML document for "+ getDisplayName(), ex);}
}
//这就是在上下文中创建DefaultListableBeanFactory的地方,而getInternalParentBeanFactory()
//的具体实现可以
//参看AbstractApplicationContext中的实现,会根据容器已有的双亲IoC容器的信息来生成
// DefaultListableBeanFactory的双亲IoC容器
protected DefaultListableBeanFactory createBeanFactory() {return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
//这里是使用BeanDefinitionReader载入Bean定义的地方,因为允许有多种载入方式,虽然用得
//最多的是XML定义的形式,这里通过一个抽象函数把具体的实现委托给子类来完成protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)throws IOException, BeansException;public int loadBeanDefinitions(String location, Set actualResources) throwsBeanDefinitionStoreException {//这里取得 ResourceLoader,使用的是DefaultResourceLoaderResourceLoader resourceLoader = getResourceLoader();if (resourceLoader == null) {throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");}
//这里对Resource的路径模式进行解析,比如我们设定的各种Ant格式的路径定义,得到需要的
//Resource集合,这些Resource集合指向我们已经定义好的BeanDefinition信息,可以是多个文件if (resourceLoader instanceof ResourcePatternResolver) {try {
//调用DefaultResourceLoader的getResource完成具体的Resource定位Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);int loadCount = loadBeanDefinitions(resources);if (actualResources != null) {for (int i = 0; i < resources.length; i++) {actualResources.add(resources[i]);}}if (logger.isDebugEnabled()) {logger.debug("Loaded " + loadCount + " bean definitions fromlocation pattern [" + location + "]");}return loadCount;}catch (IOException ex) {throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern[" + location + "]", ex);}}else {// 调用DefaultResourceLoader的getResource完成具体的Resource定位Resource resource = resourceLoader.getResource(location);int loadCount = loadBeanDefinitions(resource);if (actualResources != null) {actualResources.add(resource);}if (logger.isDebugEnabled()) {logger.debug("Loaded " + loadCount + " bean definitions from location[" + location + "]");}return loadCount;}
}
//对于取得Resource的具体过程,我们可以看看DefaultResourceLoader是怎样完成的
public Resource getResource(String location) {Assert.notNull(location, "Location must not be null");//这里处理带有classpath标识的Resourceif (location.startsWith(CLASSPATH_URL_PREFIX)) {return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());}else {try {// 这里处理URL标识的Resource定位URL url = new URL(location);return new UrlResource(url);}catch (MalformedURLException ex) {//如果既不是classpath,也不是URL标识的Resource定位,则把getResource的//重任交给getResourceByPath,这个方法是一个protected方法,默认的实现是得到//一个ClassPathContextResource,这个方法常常会用子类来实现return getResourceByPath(location);}}
}

前面我们看到的getResourceByPath会被子类FileSystemXmlApplicationContext实现,这个方法返回的是一个 FileSystemResource对象,通过这个对象,Spring可以进行相关的I/O操作,完成BeanDefinition的定位。分析到这里已经一目了然,它实现的就是对path进行解析,然后生成一个FileSystemResource对象并返回,如代码清单2-6所示。
代码清单2-6 FileSystemXmlApplicationContext生成FileSystemResource对象

protected Resource getResourceByPath(String path) {if (path != null && path.startsWith("/")) {path = path.substring(1);}return new FileSystemResource(path);
}

如果是其他的ApplicationContext,那么会对应生成其他种类的Resource,比如ClassPathResource、ServletContextResource等。关于Spring中Resource的种类,可以在图2-9的Resource类的继承关系中了解。作为接口的Resource定义了许多与I/O相关的操作,这些操作也都可以从图2-9中的Resource的接口定义中看到。这些接口对不同的Resource实现代表着不同的意义,是Resource的实现需要考虑的。Resource接口的实现在Spring中的设计如图2-9所示。


从图2-9中我们可以看到Resource的定义和它的继承关系,通过对前面的实现原理的分析,我们以FileSystemXmlApplicationContext的实现原理为例子,了解了Resource定位问题的解决方案,即以FileSystem方式存在的Resource的定位实现。在BeanDefinition定位完成的基础上,就可以通过返回的Resource对象来进行BeanDefinition的载入了。在定位过程完成以后,为BeanDefinition的载入创造了I/O操作的条件,但是具体的数据还没有开始读入。这些数据的读入将在下面介绍的BeanDefinition的载入和解析中来完成。仍然以水桶为例子,这里就像用水桶去打水,要先找到水源。这里完成对Resource的定位,就类似于水源已经找到了,下面就是打水的过程了,类似于把找到的水装到水桶里的过程。找水不简单,但是与打水相比,我们发现打水更需要技巧。
2.3.2 BeanDefinition的载入和解析
在完成对代表BeanDefinition的Resource定位的分析后,下面来了解整个BeanDefinition信息的载入过程。对IoC容器来说,这个载入过程,相当于把定义的BeanDefinition在IoC容器中转化成一个Spring内部表示的数据结构的过程。IoC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的。这些BeanDefinition数据在IoC容器中通过一个HashMap来保持和维护。当然这只是一种比较简单的维护方式,如果需要提高IoC容器的性能和容量,完全可以自己做一些扩展。
下面,从DefaultListableBeanFactory的设计入手,看看IoC容器是怎样完成BeanDefinition载入的。这个DefaultListableBeanFactory在前面已经碰到过多次,相信大家对它一定不会感到陌生。在开始分析之前,先回到IoC容器的初始化入口,也就是看一下refresh方法。这个方法的最初是在FileSystemXmlApplicationContext的构造函数中被调用的,它的调用标志着容器初始化的开始,这些初始化对象就是BeanDefinition数据,初始化入口如代码清单2-7所示。
代码清单2-7 启动BeanDefinition的载入

1. public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh,
ApplicationContext parent) throws BeansException {super(parent);setConfigLocations(configLocations);//这里调用容器的refresh,是载入BeanDefinition的入口if (refresh) {refresh();}
}

对容器的启动来说,refresh是一个很重要的方法,下面介绍一下它的实现。该方法在AbstractApplicationContext类(它是FileSystemXmlApplicationContext的基类)中找到,它详细地描述了整个ApplicationContext的初始化过程,比如BeanFactory的更新,MessageSource和PostProcessor的注册,等等。这里看起来更像是对ApplicationContext进行初始化的模板或执行提纲,这个执行过程为 Bean的生命周期管理提供了条件。熟悉IoC容器使用的读者,从这一系列调用的名字就能大致了解应用上下文初始化的主要内容。这里就直接列出代码,不做太多的解释了。这个IoC容器的refresh过程如代码清单2-8所示。
代码清单2-8 对IoC容器执行refresh的过程

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {prepareRefresh();//这里是在子类中启动refreshBeanFactory()的地方ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {//设置BeanFactoy的后置处理postProcessBeanFactory(beanFactory);//调用BeanFactory的后处理器,这些后处理器是在Bean定义中向容器注册的invokeBeanFactoryPostProcessors(beanFactory);//注册Bean的后处理器,在Bean创建过程中调用。registerBeanPostProcessors(beanFactory);//对上下文中的消息源进行初始化initMessageSource();//初始化上下文中的事件机制initApplicationEventMulticaster();//初始化其他的特殊BeanonRefresh();//检查监听Bean并且将这些Bean向容器注册registerListeners();//实例化所有的(non-lazy-init)单件finishBeanFactoryInitialization(beanFactory);//发布容器事件,结束Refresh过程finishRefresh();}catch (BeansException ex) {//为防止Bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件BeandestroyBeans();// 重置 'active'标志cancelRefresh(ex);throw ex;}}
}

进入到AbstractRefreshableApplicationContext的refreshBeanFactory()方法中,在这个方法中创建了BeanFactory。在创建IoC容器前,如果已经有容器存在,那么需要把已有的容器销毁和关闭,保证在refresh以后使用的是新建立起来的IoC容器。这么看来,这个refresh非常像重启动容器,就像重启动计算机那样。在建立好当前的IoC容器以后,开始了对容器的初始化过程,比如BeanDefinition的载入,具体的交互过程如图2-10所示。
可以从AbstractRefreshableApplicationContext的refreshBeanFactory方法开始,了解这个Bean定义信息载入的过程,具体实现如代码清单2-9所示。
代码清单2-9 AbstractRefreshableApplicationContext的refreshBeanFactory方法

protected final void refreshBeanFactory() throws BeansException {if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {//创建IoC容器,这里使用的是DefaultListableBeanFactoryDefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);//启动对BeanDefintion的载入loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing XML document for "+ getDisplayName(), ex);}
}

这里调用的loadBeanDefinitions实际上是一个抽象方法,那么实际的载入过程发生在哪里呢?我们看看前面提到的loadBeanDefinitions在AbstractRefreshableApplicationContext的子类AbstractXmlApplicationContext中的实现,在这个loadBeanDefinitions中,初始化了读取器XmlBeanDefinitionReader,然后把这个读取器在IoC容器中设置好(过程和编程式使用XmlBeanFactory是类似的),最后是启动读取器来完成BeanDefinition在IoC容器中的载入,如代码清单2-10所示。
代码清单2-10 AbstractXmlApplicationContext中的loadBeanDefinitions

public abstract class AbstractXmlApplicationContext extends
AbstractRefreshableConfigApplicationContext {public AbstractXmlApplicationContext() {}public AbstractXmlApplicationContext(ApplicationContext parent) {super(parent);
}
//这里是实现loadBeanDefinitions的地方
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {//创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中去,创建BeanFactory//的过程可以参考上文对编程式使用IoC容器的相关分析,这里和前面一样,使用的也是DefaultListableBeanFactoryXmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);//这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader配//ResourceLoader,因为DefaultResourceLoader是父类,所以this可以直接被使用beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));//这是启动Bean定义信息载入的过程initBeanDefinitionReader(beanDefinitionReader);loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}

接着就是loadBeanDefinitions调用的地方,首先得到BeanDefinition信息的Resource定位,然后直接调用 XmlBeanDefinitionReader来读取,具体的载入过程是委托给BeanDefinitionReader完成的。因为这里的BeanDefinition是通过XML文件定义的,所以这里使用XmlBeanDefinitionReader来载入BeanDefinition到容器中,如代码清单2-11所示。
代码清单2-11 XmlBeanDefinitionReader载入BeanDefinition

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws
BeansException, IOException {//以Resource的方式获得配置文件的资源位置Resource[] configResources = getConfigResources();if (configResources != null) {reader.loadBeanDefinitions(configResources);}//以String的形式获得配置文件的位置String[] configLocations = getConfigLocations();if (configLocations != null) {reader.loadBeanDefinitions(configLocations);}
}
protected Resource[] getConfigResources() {return null;
}
}

通过以上对实现原理的分析,我们可以看到,在初始化FileSystmXmlApplicationContext的过程中是通过调用IoC容器的refresh来启动整个BeanDefinition的载入过程的,这个初始化是通过定义的XmlBeanDefinitionReader来完成的。同时,我们也知道实际使用的IoC容器是DefultListableBeanFactory,具体的Resource载入在XmlBeanDefinitionReader读入BeanDefinition时实现。因为Spring可以对应不同形式的BeanDefinition。由于这里使用的是XML方式的定义,所以需要使用XmlBeanDefinitionReader。如果使用了其他的BeanDefinition方式,就需要使用其他种类的BeanDefinitionReader来完成数据的载入工作。在XmlBeanDefinitionReader的实现中可以看到,是在reader.loadBeanDefinitions中开始进行BeanDefinition的载入的,而这时XmlBeanDefinitionReader的父类AbstractBean-Definition-Reader已经为BeanDefinition的载入做好了准备,如代码清单2-12所示。
代码清单2-12 AbstractBeanDefinitionReader载入BeanDefinition

public int loadBeanDefinitions(Resource[] resources) throws
BeanDefinitionStoreException {//如果Resource为空,则停止BeanDefinition的载入//然后启动载入BeanDefinition的过程,这个过程会遍历整个Resource集合所//包含的BeanDefinition信息Assert.notNull(resources, "Resource array must not be null");int counter = 0;for (int i = 0; i < resources.length; i++) {counter += loadBeanDefinitions(resources[i]);}return counter;
}

这里调用的是loadBeanDefinitions(Resource res)方法,但这个方法在AbstractBean-DefinitionReader类里是没有实现的,它是一个接口方法,具体的实现在XmlBean-DefinitionReader中。在读取器中,需要得到代表XML文件的Resource,因为这个Resource对象封装了对XML文件的I/O操作,所以读取器可以在打开I/O流后得到XML的文件对象。有了这个文件对象以后,就可以按照Spring的Bean定义规则来对这个XML的文档树进行解析了,这个解析是交给BeanDefinitionParserDelegate来完成的,看起来实现脉络很清楚。具体可以参考代码实现,如代码清单2-13所示。
代码清单2-13 对BeanDefinition的载入实现

//这里是调用的入口
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions(new EncodedResource(resource));
}
//这里是载入XML形式的BeanDefinition的地方
public int loadBeanDefinitions(EncodedResource encodedResource) throws
BeanDefinitionStoreException {Assert.notNull(encodedResource, "EncodedResource must not be null");if (logger.isInfoEnabled()) {logger.info("Loading XML bean definitions from " + encodedResource.getResource());}Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {currentResources = new HashSet<EncodedResource>(4);this.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException("Detected recursive loading of " + encodedResource + " - check your import definitions!");}//这里得到XML文件,并得到IO的InputSource准备进行读取try {InputStream inputStream = encodedResource.getResource().getInputStream();try {InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {inputStream.close();}}catch (IOException ex) {throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);}finally {currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.set(null);}}
}
//具体的读取过程可以在doLoadBeanDefinitions方法中找到
//这是从特定的XML文件中实际载入BeanDefinition的地方
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {int validationMode = getValidationModeForResource(resource);//这里取得XML文件的Document对象,这个解析过程是由 documentLoader完成的这个//documentLoader是DefaultDocumentLoader,在定义documentLoader的地方创建Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,validationMode, isNamespaceAware());//这里启动的是对BeanDefinition解析的详细过程,这个解析会使用到Spring的Bean//配置规则,是我们下面需要详细讲解的内容return registerBeanDefinitions(doc, resource);}catch (BeanDefinitionStoreException ex) {throw ex;}catch (SAXParseException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),"Line " + ex.getLineNumber() + " in XML document from " + resource+ " is invalid", ex);}catch (SAXException ex) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),"XML document from " + resource + " is invalid", ex);}catch (ParserConfigurationException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Parser configuration exception parsing XML from " + resource, ex);}catch (IOException ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"IOException parsing XML document from " + resource, ex);}catch (Throwable ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Unexpected exception parsing XML document from " + resource, ex);}
}

感兴趣的读者可以到DefaultDocumentLoader中去看看如何得到Document对象,这里就不详细分析了。我们关心的是Spring的BeanDefinion是怎样按照Spring的Bean语义要求进行解析并转化为容器内部数据结构的,这个过程是在registerBeanDefinitions(doc, resource)中完成的。具体的过程是由BeanDefinitionDocumentReader来完成的,这个registerBeanDefinition还对载入的Bean的数量进行了统计。具体过程如代码清单2-14所示。
代码清单2-14 registerBeanDefinition的代码实现

public int registerBeanDefinitions(Document doc, Resource resource) throws
BeanDefinitionStoreException {//这里得到 BeanDefinitionDocumentReader来对XML的BeanDefinition进行解析BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();int countBefore = getRegistry().getBeanDefinitionCount();//具体的解析过程在这个registerBeanDefinitions中完成documentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;
}

BeanDefinition的载入分成两部分,首先通过调用XML的解析器得到document对象,但这些document对象并没有按照Spring的Bean规则进行解析。在完成通用的XML解析以后,才是按照Spring的Bean规则进行解析的地方,这个按照Spring的Bean规则进行解析的过程是在 documentReader中实现的。这里使用的documentReader是默认设置好的DefaultBean-DefinitionDocumentReader。这个DefaultBeanDefinitionDocumentReader的创建是在后面的方法中完成的,然后再完成BeanDefinition的处理,处理的结果由BeanDefinitionHolder对象来持有。这个BeanDefinitionHolder除了持有BeanDefinition对象外,还持有其他与BeanDefinition的使用相关的信息,比如Bean的名字、别名集合等。这个BeanDefinition-Holder的生成是通过对Document文档树的内容进行解析来完成的,可以看到这个解析过程是由BeanDefinition-ParserDelegate来实现(具体在processBeanDefinition方法中实现)的,同时这个解析是与Spring对BeanDefinition的配置规则紧密相关的。具体的实现原理如代码清单2-15所示。
代码清单2-15 创建BeanDefinitionDocumentReader

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));}//这样,得到了 documentReader以后,为具体的Spring Bean的解析过程准备好了数据//这里是处理BeanDefinition的地方,具体的处理委托给 BeanDefinitionParserDelegate来//完成,ele对应在Spring BeanDefinition中定义的XML元素protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {/* BeanDefinitionHolder是BeanDefinition对象的封装类,封装了BeanDefinition,Bean的名字和别名。用它来完成向IoC容器注册。得到这个 BeanDefinitionHolder就意味着BeanDefinition是通过BeanDefinitionParserDelegate对XML元素的信息按照Spring的Bean规则进行解析得到的*/BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 这里是向IoC容器注册解析得到BeanDefinition的地方BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex);}// 在BeanDefinition向IoC容器注册完以后,发送消息getReaderContext().fireComponentRegistered(newBeanComponentDefinition(bdHolder));}
}

具体的Spring BeanDefinition的解析是在BeanDefinitionParserDelegate中完成的。这个类里包含了对各种Spring Bean定义规则的处理,感兴趣的读者可以仔细研究。比如我们最熟悉的对Bean元素的处理是怎样完成的,也就是怎样处理在XML定义文件中出现的这个最常见的元素信息。在这里会看到对那些熟悉的BeanDefinition定义的处理,比如id、name、aliase等属性元素。把这些元素的值从XML文件相应的元素的属性中读取出来以后,设置到生成的BeanDefinitionHolder中去。这些属性的解析还是比较简单的。对于其他元素配置的解析,比如各种Bean的属性配置,通过一个较为复杂的解析过程,这个过程是由parseBeanDefinitionElement来完成的。解析完成以后,会把解析结果放到BeanDefinition对象中并设置到BeanDefinitionHolder中去,如代码清单2-16所示。
代码清单2-16 BeanDefinitionParserDelegate对Bean元素定义的处理

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition
containingBean) {//这里取得在<bean>元素中定义的id、name和aliase属性的值String id = ele.getAttribute(ID_ATTRIBUTE);String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);List<String> aliases = new ArrayList<String>();if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}String beanName = id;if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {beanName = aliases.remove(0);if (logger.isDebugEnabled()) {logger.debug("No XML 'id' specified - using '" + beanName +"' as bean name and " + aliases + " as aliases");}}if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}//这个方法会引发对Bean元素的详细解析AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {if (!StringUtils.hasText(beanName)) {try {if (containingBean != null) {beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {beanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null &&beanName.startsWith(beanClassName) && beanName.length() >beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (logger.isDebugEnabled()) {logger.debug("Neither XML 'id' nor 'name' specified - " +"using generated bean name [" + beanName + "]");}}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}String[] aliasesArray = StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;
}

上面介绍了对Bean元素进行解析的过程,也就是BeanDefinition依据XML的定义被创建的过程。这个BeanDefinition可以看成是对定义的抽象,如图2-11所示。这个数据对象中封装的数据大多都是与定义相关的,也有很多就是我们在定义Bean时看到的那些Spring标记,比如常见的init-method、destroy-method、factory-method,等等,这个BeanDefinition数据类型是非常重要的,它封装了很多基本数据,这些基本数据都是IoC容器需要的。有了这些基本数据,IoC容器才能对Bean配置进行处理,才能实现相应的容器特性。


beanClass、description、lazyInit这些属性都是在配置bean时经常碰到的,都集中在这里。这个BeanDefinition是IoC容器体系中非常重要的核心数据结构。通过解析以后,这些数据已经做好在IoC容器里大显身手的准备了。对BeanDefinition元素的处理如代码清单2-17所示,在这个过程中可以看到对Bean定义的相关处理,比如对元素attribute值的处理,对元素属性值的处理,对构造函数设置的处理,等等。
代码清单2-17 对BeanDefinition定义元素的处理

public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));//这里只读取定义的<bean>中设置的class名字,然后载入到BeanDefinition中去,只是做个//记录,并不涉及对象的实例化过程,对象的实例化实际上是在依赖注入时完成的String className = null;if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}try {String parent = null;if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}//这里生成需要的BeanDefinition对象,为Bean定义信息的载入做准备AbstractBeanDefinition bd = createBeanDefinition(className, parent);//这里对当前的Bean元素进行属性解析,并设置description的信息parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));//从名字可以清楚地看到,这里是对各种<bean>元素的信息进行解析的地方parseMetaElements(ele, bd);parseLookupOverrideSubElements(ele, bd.getMethodOverrides());parseReplacedMethodSubElements(ele, bd.getMethodOverrides());//解析<bean>的构造函数设置parseConstructorArgElements(ele, bd);//解析<bean>的property设置parsePropertyElements(ele, bd);parseQualifierElements(ele, bd);bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;}
//下面这些异常是在配置Bean出现问题时经常会看到的,原来是在这里抛出的这些检查是在
//createBeanDefinition时进行的,会检查Bean的class设置是否正确,比如这个类是否能找到catch (ClassNotFoundException ex) {error("Bean class [" + className + "] not found", ele, ex);}catch (NoClassDefFoundError err) {error("Class that bean class [" + className + "] depends on not found", ele, err);}catch (Throwable ex) {error("Unexpected failure during bean definition parsing", ele, ex);}finally {this.parseState.pop();}return null;
}
上面是具体生成BeanDefinition的地方。在这里,我们举一个对property进行解析的例子来完成对整个BeanDefinition载入过程的分析,还是在类BeanDefinitionParserDelegate的代码中,一层一层地对BeanDefinition中的定义进行解析,比如从属性元素集合到具体的每一个属性元素,然后才是对具体的属性值的处理。根据解析结果,对这些属性值的处理会被封装成PropertyValue对象并设置到BeanDefinition对象中去,如代码清单2-18所示。
代码清单2-18   对BeanDefinition中Property元素集合的处理
// 这里对指定Bean元素的property子元素集合进行解析
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {//遍历所有Bean元素下定义的property元素NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element && DomUtils.nodeNameEquals(node,PROPERTY_ELEMENT)) {//在判断是property元素后对该property元素进行解析的过程parsePropertyElement((Element) node, bd);}}
}
public void parsePropertyElement(Element ele, BeanDefinition bd) {//这里取得property的名字String propertyName = ele.getAttribute(NAME_ATTRIBUTE);if (!StringUtils.hasLength(propertyName)) {error("Tag 'property' must have a 'name' attribute", ele);return;}this.parseState.push(new PropertyEntry(propertyName));try {//如果同一个Bean中已经有同名的property存在,则不进行解析,直接返回。也就是说,//如果在同一个Bean中有同名的property设置,那么起作用的只是第一个if (bd.getPropertyValues().contains(propertyName)) {error("Multiple 'property' definitions for property '"+ propertyName + "'", ele);return;}//这里是解析property值的地方,返回的对象对应对Bean定义的property属性设置的//解析结果,这个解析结果会封装到PropertyValue对象中,然后设置到BeanDefinitionHolder中去Object val = parsePropertyValue(ele, bd, propertyName);PropertyValue pv = new PropertyValue(propertyName, val);parseMetaElements(ele, pv);pv.setSource(extractSource(ele));bd.getPropertyValues().addPropertyValue(pv);}finally {this.parseState.pop();}
}
//这里取得property元素的值,也许是一个list或其他
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {String elementName = (propertyName != null) ?"<property> element for property '" + propertyName + "'" :"<constructor-arg> element";NodeList nl = ele.getChildNodes();Element subElement = null;for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element && !DomUtils.nodeNameEquals(node, DESCRIPTION_ELEMENT) &&!DomUtils.nodeNameEquals(node, META_ELEMENT)) {if (subElement != null) {error(elementName + " must not contain more than one sub-element", ele);}else {subElement = (Element) node;}}}//这里判断property的属性,是ref还是value,不允许同时是ref和valueboolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);if ((hasRefAttribute && hasValueAttribute) ||((hasRefAttribute || hasValueAttribute) && subElement != null)) {error(elementName +" is only allowed to contain either 'ref' attribute OR'value' attribute OR sub-element", ele);}//如果是ref,创建一个ref的数据对象RuntimeBeanReference,这个对象封装了ref的信息if (hasRefAttribute) {String refName = ele.getAttribute(REF_ATTRIBUTE);if (!StringUtils.hasText(refName)) {error(elementName + " contains empty 'ref' attribute", ele);}RuntimeBeanReference ref = new RuntimeBeanReference(refName);ref.setSource(extractSource(ele));return ref;} //如果是value,创建一个value的数据对象TypedStringValue ,这个对象封装了value的信息else if (hasValueAttribute) {TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));valueHolder.setSource(extractSource(ele));return valueHolder;}//如果还有子元素,触发对子元素的解析else if (subElement != null) {return parsePropertySubElement(subElement, bd);}else {error(elementName + " must specify a ref or value", ele);return null;}
}

这里是对property子元素的解析过程,Array、List、Set、Map、Prop等各种元素都会在这里进行解析,生成对应的数据对象,比如ManagedList、ManagedArray、ManagedSet等。这些Managed类是Spring对具体的BeanDefinition的数据封装。具体的解析过程读者可以去查看自己感兴趣的部分,比如 parseArrayElement、parseListElement、parseSetElement、parseMapElement、parsePropElement对应着不同类型的数据解析,同时这些具体的解析方法在BeanDefinitionParserDelegate类中也都能够找到。因为方法命名很清晰,所以从方法名字上就能够很快地找到。下面以对Property的元素进行解析的过程为例,通过它的实现来说明具体的解析过程是怎样完成的,如代码清单2-19所示。
代码清单2-19 对属性元素进行解析

public Object parsePropertySubElement(Element ele, BeanDefinition bd, String
defaultValueType) {if (!isDefaultNamespace(ele.getNamespaceURI())) {return parseNestedCustomElement(ele, bd);}else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) {BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);if (nestedBd != null) {nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);}return nestedBd;}else if (DomUtils.nodeNameEquals(ele, REF_ELEMENT)) {String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);boolean toParent = false;if (!StringUtils.hasLength(refName)) {refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE);if (!StringUtils.hasLength(refName)) {refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);toParent = true;if (!StringUtils.hasLength(refName)) {error("'bean', 'local' or 'parent' is required for <ref> element", ele);return null;}}}if (!StringUtils.hasText(refName)) {error("<ref> element contains empty target attribute", ele);return null;}RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);ref.setSource(extractSource(ele));return ref;}else if (DomUtils.nodeNameEquals(ele, IDREF_ELEMENT)) {return parseIdRefElement(ele);}else if (DomUtils.nodeNameEquals(ele, VALUE_ELEMENT)) {return parseValueElement(ele, defaultValueType);}else if (DomUtils.nodeNameEquals(ele, NULL_ELEMENT)) {TypedStringValue nullHolder = new TypedStringValue(null)nullHolder.setSource(extractSource(ele));return nullHolder;}else if (DomUtils.nodeNameEquals(ele, ARRAY_ELEMENT)) {return parseArrayElement(ele, bd);}else if (DomUtils.nodeNameEquals(ele, LIST_ELEMENT)) {return parseListElement(ele, bd);}else if (DomUtils.nodeNameEquals(ele, SET_ELEMENT)) {return parseSetElement(ele, bd);}else if (DomUtils.nodeNameEquals(ele, MAP_ELEMENT)) {return parseMapElement(ele, bd);}else if (DomUtils.nodeNameEquals(ele, PROPS_ELEMENT)) {return parsePropsElement(ele);}else {error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);return null;}
}

下面看看List这样的属性配置是怎样被解析的,依然是在BeanDefinitionParserDelegate中,返回的是一个List对象,这个List是Spring定义的ManagedList,作为封装List这类配置定义的数据封装,如代码清单2-20所示。
代码清单2-20 解析BeanDefinition中的List元素

public List parseListElement(Element collectionEle, BeanDefinition bd) {String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE);NodeList nl = collectionEle.getChildNodes();ManagedList<Object> target = new ManagedList<Object>(nl.getLength());target.setSource(extractSource(collectionEle));target.setElementTypeName(defaultElementType);target.setMergeEnabled(parseMergeAttribute(collectionEle));//具体的List元素的解析过程parseCollectionElements(nl, target, bd, defaultElementType);return target;
}
protected void parseCollectionElements(NodeList elementNodes, Collection<Object> target, BeanDefinition bd,String defaultElementType) {//遍历所有的元素节点,并判断其类型是否为Elementfor (int i = 0; i < elementNodes.getLength(); i++) {Node node = elementNodes.item(i);if (node instanceof Element && !DomUtils.nodeNameEquals(node, DESCRIPTION_ELEMENT)) {//加入到target中,target是一个ManagedList,同时触发对下一层子元素的解析过程,//这是一个递归的调用target.add(parsePropertySubElement((Element) node, bd, defaultElementType));}}
}

经过这样逐层地解析,我们在XML文件中定义的BeanDefinition就被整个载入到了IoC容器中,并在容器中建立了数据映射。在IoC容器中建立了对应的数据结构,或者说可以看成是POJO对象在IoC容器中的抽象,这些数据结构可以以AbstractBeanDefinition为入口,让IoC容器执行索引、查询和操作。简单的POJO操作背后其实蕴含着一个复杂的抽象过程,经过以上的载入过程,IoC容器大致完成了管理Bean对象的数据准备工作(或者说是初始化过程)。但是,重要的依赖注入实际上在这个时候还没有发生,现在,在IoC容器BeanDefinition中存在的还只是一些静态的配置信息。严格地说,这时候的容器还没有完全起作用,要完全发挥容器的作用,还需完成数据向容器的注册。
2.3.3 BeanDefinition在IoC容器中的注册
前面已经分析过BeanDefinition在IoC容器中载入和解析的过程。在这些动作完成以后,用户定义的BeanDefinition信息已经在IoC容器内建立起了自己的数据结构以及相应的数据表示,但此时这些数据还不能供IoC容器直接使用,需要在IoC容器中对这些BeanDefinition数据进行注册。这个注册为IoC容器提供了更友好的使用方式,在DefaultListableBeanFactory中,是通过一个HashMap来持有载入的BeanDefinition的,这个HashMap的定义在DefaultListableBeanFactory中可以看到,如下所示。

/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new
ConcurrentHashMap<String, BeanDefinition>();

将解析得到的BeanDefinition向IoC容器中的beanDefinitionMap注册的过程是在载入BeanDefinition完成后进行的,注册的调用过程如图2-12所示。


从源代码实现的角度,可以看到相关的调用关系如图2-13所示。
我们跟踪以上的代码调用去看一下具体的注册实现,在DefaultListableBeanFactory中实现了BeanDefinitionRegistry的接口,这个接口的实现完成BeanDefinition向容器的注册。这个注册过程不复杂,就是把解析得到的BeanDefinition设置到hashMap中去。需要注意的是,如果遇到同名的BeanDefinition,进行处理的时候需要依据allowBeanDefinitionOverriding的配置来完成。具体的实现如代码清单2-21所示。
图2-13 registerBeanDefinition的调用关系
代码清单2-21 BeanDefinition注册的实现

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws
BeanDefinitionStoreException {Assert.hasText(beanName, "'beanName' must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition. getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}//注册的过程需要synchronized,保证数据的一致性synchronized (this.beanDefinitionMap) {//这里检查是不是有相同名字的BeanDefinition已经在IoC容器中注册了,如果有相同名字的//BeanDefinition,但又不允许覆盖,那么会抛出异常Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);if (oldBeanDefinition != null) {if (!this.allowBeanDefinitionOverriding) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Cannot register bean definition [" +beanDefinition + "] for bean '" + beanName +"': There is already [" + oldBeanDefinition + "] bound.");}else {if (this.logger.isInfoEnabled()) {this.logger.info("Overriding bean definition for bean'" + beanName +"': replacing [" + oldBeanDefinition + "]with [" + beanDefinition + "]");}}}
/*这是正常注册BeanDefinition的过程,把Bean的名字存入到beanDefinitionNames的同时,把
beanName作为Map的key,把beanDefinition作为value存入到IoC容器持有的beanDefinitionMap中去*/else {this.beanDefinitionNames.add(beanName);this.frozenBeanDefinitionNames = null;}this.beanDefinitionMap.put(beanName, beanDefinition);resetBeanDefinition(beanName);}
}

完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时,在使用的IoC容器DefaultListableBeanFactory中已经建立了整个Bean的配置信息,而且这些BeanDefinition已经可以被容器使用了,它们都在beanDefinitionMap里被检索和使用。容器的作用就是对这些信息进行处理和维护。这些信息是容器建立依赖反转的基础,有了这些基础数据,下面我们看一下在IoC容器中,依赖注入是怎样完成的。

《Spring技术内幕》——2.3节IoC容器的初始化过程相关推荐

  1. [Spring 深度解析]第7章 IoC容器的初始化过程

    7. IoC容器的初始化过程 ​ 简单来说,IoC容器的初始化是由前面介绍的refresh()方法来启动的,这个方法标志着IoC容器的正式启动.具体来说,这个启动包括BeanDefinition的Re ...

  2. Spring IOC学习心得之IOC容器的初始化过程

    注:本文大多数内容都是摘自<Spring技术内幕>这本书 简单来说,Ioc容器的初始化过程是在refresh()方法中启动的,包括BeanDefinition的Resource定位,载入和 ...

  3. Spring IoC(二)IoC容器的初始化过程

    (一)IoC 容器初始化过程概述 1.1简要概述初始化过程 IoC 容器的初始化过程是通过refresh() 方法来启动的,这个方法标识着IoC 容器正式启动.具体来说,这个启动过程包括:BeanDe ...

  4. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  5. IOC原理之IoC容器的初始化过程

    IoC容器的初始化过程包括Resource定位.BeanDefinition的载入以及向IoC容器注册这些BeanDefinition三个阶段. IoC容器的初始化过程概要 IoC容器的初始化包括三个 ...

  6. Spring IoC容器的初始化过程

    转载自:http://blog.csdn.net/u010723709/article/details/47046211 原题是:2 IOC容器初始化过程 作者:@小小旭GISer ========= ...

  7. 从源码深处体验Spring核心技术--基于Xml的IOC容器的初始化

    IOC 容器的初始化包括 BeanDefinition 的 Resource 定位.加载和注册这三个基本的过程. 我们以ApplicationContext 为例讲解,ApplicationConte ...

  8. Spring 技术内幕读书笔记

    Spring的设计理念和整体架构 1.1 spring的各个子项目 1.1.1 spring framwork 核心, IoC容器设计,控制反转,AOP ,MVC ,JDBC ,事务处理 1.1.2 ...

  9. Spring(二)IOC容器的初始化流程

    文章目录 一.Spring 核心容器类 1.1 BeanFactory 1.2 ApplicationContext 1.3 BeanDefinition 二.IOC容器的初始化 2.1 基于Xml的 ...

最新文章

  1. 微信小程序导航栏设置透明
  2. Atom ctrl+atl+b 快捷键修复
  3. mysql 分词搜索_实战 | canal 实现Mysql到Elasticsearch实时增量同步
  4. 1091 N-自守数 (15 分)
  5. 飞鸽传书 的内置的计算机处理
  6. Django model update的各种用法介绍
  7. 算法笔记--STL中的各种遍历及查找(待增)
  8. java treeset 删除_删除Java TreeSet中的最低元素
  9. linux 命令修改uid,修改Linux用户的UID、GID
  10. Python自动化小米手环运动数据导出
  11. 安卓app开机自启动的几种方式
  12. HCIP-DATACOM H12-831(41-60)
  13. dbca静默建库踩坑
  14. 济南ITSS证书办理大全
  15. html5判断出生日期,出生时间看五行,出生日期查五行属性?
  16. 【译文】四十二种谬误(一)
  17. android模拟打印机服务,Android下的POS打印机调用的简单实现
  18. 省流版-38号车评中心历史车评文字汇总
  19. 计算机无法外接投影,笔记本电脑连接投影仪无信号解决步骤
  20. C语言实现设计模式-策略模式+命令模式组合使用

热门文章

  1. 上传文件到服务器地址怎么配置,文件上传到服务器怎么配置
  2. 调用$.ajax不成功,jquery中ajax请求后台数据成功后既不执行success也不执行error的完美解决方法...
  3. 计算机教师的幸福,如何成为一名幸福信息技术教师
  4. springboot日志配输出路径配置_Spring Boot 日志配置方法(超详细)
  5. 面试问题_教资面试,结构化面试问题分享
  6. 防统方系统服务器的拼音,横渡医院防统方系统软件技术参数(最新)
  7. 怎么把文件上传云服务器上,如何把文件上传到云服务器上
  8. r生成html文件,从R中的许多html文件创建一个语料库
  9. Web MIDI API W3C
  10. ndarray.ravel([order]) 和 ndarray.flatten([order])