在Spring IoC
容器的设计中,有两个主要的容器系列。一个是实现了BeanFactory
接口的简单容器系列,这系列容器只实现了容器基本的功能;另外一个是ApplicationContext
应用上下文,它在简单容器的基础上增长了许多面向框架的特性,同时对应用环境作了许多适配。java
Spring IoC
容器的初始化过程分为三个阶段:Resource
定位、BeanDefinition
的载入和向IoC
容器注册BeanDefinition
。Spring
把这三个阶段分离,并使用不一样的模块来完成,这样可让用户更加灵活的对这三个阶段进行扩展。数据结构
Resource
定位指的是BeanDefinition
的资源定位,它由ResourceLoader
经过统一的Resource
接口来完成,Resource
对各类形式的BeanDefinition
的使用都提供了统一的接口。BeanDefinition
的载入是把用户定义好的Bean
表示成IoC
容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition
,BeanDefinition
实际上就是POJO
对象在IoC
容器中的抽象。经过BeanDefinition
,IoC
容器能够方便的对POJO
对象进行管理。IoC
容器注册BeanDefinition
是经过调用BeanDefinitionRegistry
接口的实现来完成的,这个注册过程是把载入的BeanDefinition
向IoC
容器进行注册。实际上,在IoC
容器内部维护着一个HashMap
,而这个注册过程其实就将BeanDefinition
添加至这个HashMap
。咱们能够本身定义Resource
、BeanFactory
和BeanDefinitionReader
来初始化一个容器。以下代码片断使用了DefaultListableBeanFactory
做为实际使用的IoC
容器。同时,建立IoC
配置文件(dispatcher-servlet.xml
)的抽象资源,这个抽象资源包含了BeanDefinition
的定义信息。最后,还须要建立一个载入BeanDefinition
的读取器,此处使用XmlBeanDefinitionReader
,经过一个回调配置给BeanFactory
。框架
ClassPathResource res = new ClassPathResource("dispatcher-servlet.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);复制代码
咱们也能够经过ApplicationContext
建立一个IoC
容器。在Spring
中,系统已经提供许多定义好的容器实现,而不须要本身组装。以下代码片断以FileSystemXmlApplicationContext
为例建立了一个IoC
容器。post
FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext("classpath:dispatcher-servlet.xml");复制代码
不管使用哪一种方式初始化IoC
容器,都会经历上述三个阶段。本篇文章将结合Spring 4.0.2
源码,并以FileSystemXmlApplicationContext
为例对IoC
容器初始化的第一阶段,也就是Resource
定位阶段进行分析。this
下图展现了FileSystemXmlApplicationContext
的继承体系,FileSystemXmlApplicationContext
继承自AbstractApplicationContext
,而AbstractApplicationContext
又继承自DefaultResourceLoader
,DefaultResourceLoader
实现了ResourceLoader
接口。所以FileSystemXmlApplicationContext
具有读取定义了BeanDefinition
的Resource
的能力。spa
咱们的分析入口是new FileSystemXmlApplicationContext("classpath:dispatcher-servlet.xml");
,这句代码调用了FileSystemXmlApplicationContext
的构造方法。FileSystemXmlApplicationContext
的构造方法源码以下(只提取与本次分析关联的代码)。debug
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, (ApplicationContext)null);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if(refresh) {
this.refresh();
}
}复制代码
在建立FileSystemXmlApplicationContext
时,咱们仅传入了包含BeanDefinition
的配置文件路径(classpath:dispatcher-servlet.xml
),由此调用FileSystemXmlApplicationContext(String configLocation)
构造方法。接着,FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
构造方法被间接调用,在该构造方法内部,refresh
方法完成了整个IoC
容器的初始化。所以,refresh
方法是咱们分析的下一个入口。设计
refresh
方法的具体实现定义在FileSystemXmlApplicationContext
的父类AbstractApplicationContext
中,对应的源码以下。code
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var5) {
this.destroyBeans();
this.cancelRefresh(var5);
throw var5;
}
}
}复制代码
在refresh
方法中,经过obtainFreshBeanFactory
方法,ConfigurableListableBeanFactory
类型的BeanFactory
被建立。咱们接着进入obtainFreshBeanFactory
方法,obtainFreshBeanFactory
方法也定义在AbstractApplicationContext
中。orm
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;复制代码
咱们重点关注refreshBeanFactory
方法的实现。在AbstractApplicationContext
中,refreshBeanFactory
方法仅仅是个声明,具体的实现委托给了子类完成。此处,refreshBeanFactory
方法的具体实现定义在了AbstractRefreshableApplicationContext
,AbstractRefreshableApplicationContext
正是继承自AbstractApplicationContext
,这点咱们能够从上文的继承体系图能够得知。refreshBeanFactory
方法在AbstractRefreshableApplicationContext
中的定义以下。
protected final void refreshBeanFactory() throws BeansException {
if(this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
DefaultListableBeanFactory ex = this.createBeanFactory();
ex.setSerializationId(this.getId());
this.customizeBeanFactory(ex);
this.loadBeanDefinitions(ex);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = ex;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory var1) throws BeansException, IOException;复制代码
refreshBeanFactory
方法首先会判断是否已经创建的BeanFactory
,若是已经创建,那么须要销毁并关闭该BeanFactory
。接着,refreshBeanFactory
方法经过createBeanFactory
方法建立了一个IoC
容器供ApplicationContext
使用,且这个IoC
容器的实际类型为DefaultListableBeanFactory
。同时,refreshBeanFactory
方法将这个IoC
容器做为参数,调用loadBeanDefinitions
载入了BeanDefinition
(本文暂不分析载入过程的具体操做)。
loadBeanDefinitions
方法也仅仅在AbstractRefreshableApplicationContext
中声明,具体的实现定义在AbstractXmlApplicationContext
中,从继承体系图咱们能够得知AbstractXmlApplicationContext
正是AbstractRefreshableApplicationContext
的子类。loadBeanDefinitions
方法对应的源码以下。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
this.loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = this.getConfigResources();
if(configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = this.getConfigLocations();
if(configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}复制代码
在loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
中,定义了BeanDefinition
的读入器beanDefinitionReader
。Spring
把定位、读入和注册的过程解耦,这正是体现之处之一。接着beanDefinitionReader
做为参数,调用loadBeanDefinitions(XmlBeanDefinitionReader reader)
方法,若是configResources
为空,那么reader
就会根据configLocations
调用reader
的loadBeanDefinitions
去加载相应的Resource
。在AbstractBeanDefinitionReader
和XmlBeanDefinitionReader
中个自定义了不一样的loadBeanDefinitions
方法,与咱们本次分析相关的代码定义在AbstractBeanDefinitionReader
中,以下所示。
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
String[] var3 = locations;
int var4 = locations.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
counter += this.loadBeanDefinitions(location);
}
return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(location, (Set)null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = this.getResourceLoader();
if(resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int loadCount;
if(!(resourceLoader instanceof ResourcePatternResolver)) {
Resource var11 = resourceLoader.getResource(location);
loadCount = this.loadBeanDefinitions((Resource)var11);
if(actualResources != null) {
actualResources.add(var11);
}
if(this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
} else {
try {
Resource[] resource =
((ResourcePatternResolver)resourceLoader).getResources(location);
loadCount = this.loadBeanDefinitions(resource);
if(actualResources != null) {
Resource[] var6 = resource;
int var7 = resource.length;
for(int var8 = 0; var8 < var7; ++var8) {
Resource resource1 = var6[var8];
actualResources.add(resource1);
}
}
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);
}
}
}
}复制代码
在loadBeanDefinitions(String location, Set<Resource> actualResources)
方法中,咱们能够看到,Resource
的定位工做交给了ResourceLoader
来完成。对于取得Resource
的具体过程,咱们能够看看DefaultResourceLoader
是怎样完成的,对应源码以下。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if(location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()),
this.getClassLoader());
} else {
try {
URL ex = new URL(location);
return new UrlResource(ex);
} catch (MalformedURLException var3) {
return this.getResourceByPath(location);
}
}
}复制代码
因为咱们传入的location
为classpath:dispatcher-servlet.xml
,所以getResource
方法会生成一个ClassPathResource
并返回,若是咱们传入的是一个文件路径,那么会调用getResourceByPath
方法,getResourceByPath
方法定义在FileSystemXmlApplicationContext
中,对应的源码以下。
protected Resource getResourceByPath(String path) {
if(path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}复制代码
到此,咱们完成了IoC
容器在初始化过程当中的Resource
定位过程的流程分析,这为接下来进行BeanDefinition
数据的载入和解析创造了条件。
后续我会对BeanDefinition
的载入和解析过程结合源码进行分析,欢迎关注。若本文存在分析不妥之处,建议发送邮件至tinylcy (at) gmail.com
交流。