在IoC容器实现系列的上一篇中,我们简单了解了IoC容器的两大系列:BeanFactory和ApplicationContext系列。了解了它们的设计思想与应用场景。在本篇博文中,我们将继续探索IoC容器的初始化过程。
在上一篇中,我们知道IoC的初始化过程是由refresh()方法启动的,启动过程包括BeanDefinition的Resource定位、载入和注册三个过程。Spring将三个过程分开,使用不同的模块来完成,使得用户可以更加灵活的对三个过程进行剪裁或扩展,定义更加符合自己需求的IoC初始化过程。
- Resource定位过程,指的是BeanDefinition的资源定位,定位过程类似于容器寻找数据的过程。由ResourceLoader统一的Resource接口来完成。
- BeanDefinition的载入,将用户定义好的Bean表示成IoC容器的内部数据结构(BeanDefinition)。通过BeanDefinition,使得IoC容器可以方便的对POJO对象进行管理。
- BeanDefinition的注册,通过调用BeanDefinitionRegistry接口的实现来完成。其实就是将BeanDefinition注入到一个HashMap中,IoC容器就是通过HashMap来持有这些BeanDefinition数据。
注意:IoC容器的初始化过程,一般不包含Bean依赖注入的实现。Bean定义的载入和依赖注入是两个独立的过程。
1 BeanDefinition的Resource定位
在定位BeanDefinition是,如果使用纯粹的IoC容器,例如DefaultListableBeanFactory,则需要为它配置特定的读取器才能完成读取BeanDefinition的功能,但是这些更底层的容器可以提高定制IoC的灵活性。
在这里,我们以FileSystemXmlApplicationContext为例,通过它分析ApplicationContext的实现是如何实现Resource的定位过程。下图为ApplicationContext的继承体系。
从源码实现角度,近距离关心以FileSystemXmlApplicationContext为核心的继承体系,如下图所示。
从中可以看到FileSystemXmlApplicationContext通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力。下面具体看看FileSystemXmlApplicationContext是如何实现的。
package org.springframework.context.support;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
/*
FileSystemXmlApplicationContext使用的IoC容器是DefaultListableBeanFactory
*/
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
public FileSystemXmlApplicationContext() {
}
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
// 这个构造函数的configuration包含的是BeanDefinition所在的文件路径
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, (ApplicationContext)null);
}
// 这个构造函数允许configuration包含多个BeanDefinition的文件路径
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
// 这个构造函数允许configuration包含多个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, (ApplicationContext)null);
}
// 在对象初始化过程中,调用refresh函数载入BeanDefinition
// 这个refresh启动了BeanDefinition的载入过程,将在下面进行详细分析
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
// 应用文件系统中的Resource实现,通过构造一个FileSystemResource来得到一个在文件系统中定位的BeanDefinition
// getResourceByPath是在BeanDefinitionReader的loadBeanDefinition中被调用的。
// loadBeanDefinition采用了模板模式,具体的定位实现实际上是由各个子类来完成的。
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}
通过上述代码,我们可以看到以XML文件方式存在的BeanDefinition都能够得到有效的处理,并且在构造方法中通过refresh方法来启动IoC容器的初始化。
注意:FileSystempplicationContextContext是一个支持XML定义的BeanDefinition的ApplicationContext,可以指定以文件的形式读入BeanDefinition。在测试环境和独立应用环境中,这个ApplicationContext都是十分有用的。
对BeanDefinition资源定位的过程是由refresh方法来触发的,大致调用过程如下图所示。
在读入BeanDefinition的过程中需要使用BeanDefinitionReader,而关于这个读入器的配置,可以到FileSystemXmlApplicationContext的父类AbstractRefreshableApplicationContext中看看他是如何实现的。如果是其他类型的ApplicationContext,则会生成其他种类的Resource。
现在我们重点研究AbstractRefreshableApplicationContext的refreshBeanFactory方法的实现。它通过调用createBeanFactory创建一个IoC容器供ApplicationContext使用,同时它启动了loadBeanDefinitions来载入BeanDefinition。
protected final void refreshBeanFactory() throws BeansException {
// 如果原来已经有BeanFactory,则销毁并关闭,保证每次refreshBeanFactory后产生的为新的BeanFactory
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
// 这里创建并设置持有的DefaultListableBeanFactory的地方,同时并调用loadBeanDefinitions载入BeanDefinition信息
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory(); // 创建IoC容器
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
this.loadBeanDefinitions(beanFactory); // 启动对BeanDefinition的载入
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
// 这里就是在上下文中创建DefaultListableBeanFactory的地方,getInternalParentBeanFactory()的
// 具体实现可以参见AbstractApplicationContext中的实现,会根据容器已有的双亲IoC容器来生成
// DefaultListableBeanFactory的双亲IoC容器
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}
// 这里的载入Bean定义的有很多种方式载入,所以为抽象方法,交给具体容器完成相应的功能,委托给子类完成。
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory var1) throws BeansException, IOException;
其中具体资源的载入在XmlBeanDefinitionReader读入BeanDefinition时完成,具体的loadBeanDefinitions可以在XmlBeanDefinitionRead的父类AbstractBeanDefinitionReader中看到,如下所示。
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 此处的ResourceLoader,使用的是DefaultResourceLoader
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
// 用于记录载入Bean的个数
int loadCount;
// 调用DefaultResourceLoader的getResource完成具体的Resource定位
if (!(resourceLoader instanceof ResourcePatternResolver)) {
Resource resource = resourceLoader.getResource(location);
loadCount = this.loadBeanDefinitions((Resource)resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
} else {
// 通过对Resource的路径进行解析,得到Resource集合,这些集合指向定义好的BeanDefinition信息,可以使多个文件
try {
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
loadCount = this.loadBeanDefinitions(resources);
if (actualResources != null) {
Resource[] var6 = resources;
int var7 = resources.length;
for(int var8 = 0; var8 < var7; ++var8) {
Resource resource = var6[var8];
actualResources.add(resource);
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
} catch (IOException var10) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
}
}
}
}
// 对于取得Resource的具体过程,可以参考DefaultResourceLoader是怎样完成的。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
Iterator var2 = this.protocolResolvers.iterator();
Resource resource;
do {
if (!var2.hasNext()) {
// 对路径的处理
if (location.startsWith("/")) {
return this.getResourceByPath(location);
}
// 这里处理带有classpath标识的Resource
if (location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
}
// 这里处理URL表示的Resource定位
try {
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException var5) {
// 如果既不是classpath,也不是URL表示的Resource,也不是普通的path,就将任务交给getResourceByPath()
// 该方法为protected方法,默认实现是得到一个ClassPathContextResource,这个方法通常会用子类来实现
return this.getResourceByPath(location);
}
}
ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
resource = protocolResolver.resolve(location, this);
} while(resource == null);
return resource;
}
在BeanDefinition的定位基础上,通过上面代码中返回的Resource对象就可以来进行BeanDefinition的载入工作了。下面,我们将开始介绍BeanDefinition的载入和解析。
2 BeanDefinition的载入和解析
在第一小节中完成了对BeanDefinition的定位后,就到了整个BeanDefinition信息的载入过程,即把定义的BeanDefinition转化为一个Spring内部表示的数据结构的过程。这些BeanDefinition数据在IoC中通过一个HashMap进行保持和维护。
我们依然从DefaultListableBeanFactory的设计入手,探索IoC容器是如何完成对BeanDefinition的载入的。在前面我们知道由refresh函数启动了IoC容器的初始化,现在我们来简单介绍一下它的实现。
该方法在AbstractApplicationContext中,详细地描述了整个ApplicationContext的初始化过程,如BeanFactory的更新,MessageSource和PostProcessor的注册等。这个过程为Bean的声明周期提供了条件,具体代码如下所示。
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
// 加锁,保证线程间一致性
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
// 启动子类中refreshBeanFactory()方法。
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 准备应用于上下文的BeanFactory
this.prepareBeanFactory(beanFactory);
try {
// 设置BeanFactory的后置处理
this.postProcessBeanFactory(beanFactory);
// 调用BeanFactory的后置处理器,这些后置处理器是在Bean定义总向容器中注册的。
this.invokeBeanFactoryPostProcessors(beanFactory);
// 注册Bean的后处理器,在Bean创建过程中调用。
this.registerBeanPostProcessors(beanFactory);
// 初始化上下文消息源
this.initMessageSource();
// 初始化上下文的事件机制
this.initApplicationEventMulticaster();
// 初始化其他特殊Bean
this.onRefresh();
// 检查监听Bean,并且将这些Bean向容器中注册
this.registerListeners();
// 实例化所有的(non-lazy-init)单件
this.finishBeanFactoryInitialization(beanFactory);
// 发布容器事件,结束refresh过程
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
// 为了防止Bean资源占用,在异常处理中销毁已经在前面生成的Bean单件,并且重置‘active’标志
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
进入refreshBeanFactory方法后,创建新的BeanFactory。实例代码已经在上文中出现过,这里边不再赘述。在建立好IoC容器后,就开始了初始化过程,比如BeanDefinition的载入,具体的交互过程如下图所示。
这里调用的loadBeanDefinitions实际上为一个抽象方法,交给各个具体的容器实现该方法。在XML方式载入过程中,这个方法中在AbstractXmlApplicationContext实现,在这个loadBeanDefinitions中,初始化了读取器XmlBeanDefinitionReader,然后把读取器在IoC容器设置好,最后启动读取器来完成BeanDefinition的载入。
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
// 此处省略了类中的其他构造方法,这里是实现loadBeanDefinitions的地方,即根据上一小节中refreshBeanFactory得到的
// BeanFactory将其中的BeanDefinitions信息载入IoC容器。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创建XmlBeanDefinitionReader,并设置到BeanFactory中。此处使用的BeanFactory也是DefaultListableBeanFactory
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader配置ResourceLoader
// 因为DefaultResourceLoader是父类,所以可以直接使用this
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 启动Bean定义信息的载入过程
this.initBeanDefinitionReader(beanDefinitionReader);
this.loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 首先以Resource的方式得到配置文件资源的位置信息,看是否存在
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 以String的形式获得配置文件的位置
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
protected Resource[] getConfigResources() {
return null;
}
}
在这里我们仅仅使用XmlBeanDefinitionReader作为示例说明,因为Spring可以对应不同形式的BeanDefinition。如果使用了其他形式的BeanDefinition,则需要使用其他种类的BeanDefinitionReader完成数据载入工作。下面我们就来看看具体载入BeanDefinition的过程。因为在AbstractBeanDefinitionReader中loadBeanDefinitions方法为抽象方法,所有应该到具体的实现读取器中方法查看相应代码,代码清单如下所示。
// 这是调用的入口方法
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}
// 以XML形式载入BeanDefinition
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isInfoEnabled()) {
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
// 这里小编不是特别明白为什么HashSet的初始大小为4?求解
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 如果将当前encodedResource加入到HashSet失败,则抛出异常。
if (!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var5;
// 这里得到XML文件,并得到IO的InputSource,准备进行读取。
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 具体调用doLoadBeanDefinitions完成相应的BeanDefinition的载入
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if (((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
return var5;
}
}
public int loadBeanDefinitions(InputSource inputSource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(inputSource, "resource loaded through SAX InputSource");
}
public int loadBeanDefinitions(InputSource inputSource, String resourceDescription) throws BeanDefinitionStoreException {
return this.doLoadBeanDefinitions(inputSource, new DescriptiveResource(resourceDescription));
}
// 具体的读取过程,从特定的XML文件中实际载入BeanDefinition的地方
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
// 调用doLoadDocument方法得到XML文件的Doucment对象
Document doc = this.doLoadDocument(inputSource, resource);
// 启动对BeanDefinition的详细解析。
return this.registerBeanDefinitions(doc, resource);
} catch (BeanDefinitionStoreException var4) {
throw var4;
} catch (SAXParseException var5) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
} catch (SAXException var6) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
} catch (ParserConfigurationException var7) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
} catch (IOException var8) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
} catch (Throwable var9) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
}
}
// 得到XML文件的Document对象,解析过程有documentLoader完成。
// documentLoader是DefaultDocumentLoader在定义documentLoader的地方创建
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}
在调用了registerBeanDefinitions()方法后,开始对BeanDefinition进行详细解析,而且此方法对载入的Bean还做了数量统计。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 得到BeanDefinitionDocumentReader对XML的BeanDefinition进行解析
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
int countBefore = this.getRegistry().getBeanDefinitionCount();
// 具体的解析过程在registerBeanDefinitions中完成
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
// 返回载入Bean的个数
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
本篇博文就先到这里吧,虽然还没有写完,但本篇博文实在有点长了,所以在下一篇博文中我们将继续分析IoC初始化过程,总结也就在下一篇写啦。