天天看点

《Spring技术内幕》学习笔记3——IoC容器载入Bean定义资源文件

1.当Spring的IoC容器将Bean定义的资源文件封装为Spring的Resource之后,接下来要做的就是通过Spring的资源加载器(resourceLoader)读入Bean定义资源文件的过程。对于IoC容器来说,Bean定义的载入过程就是将Bean定义资源文件读入进内存并解析转换成Spring所管理的Bean的数据结构的过程。相对于SpringIoC容器定位Bean定义资源文件来说,Bean定义资源文件的载入和解析过程更复杂一些,因此按照程序的运行步骤逐条分析其实现原理。

2.以FileSystemXmlApplicationContext为例分析其载入和解析Bean定义资源文件的过程:

首先从FileSystemXmlApplicationContext的入口构造函数分析起,其代码如下:

//FileSystemXmlApplicationContext IoC容器进行初始化的入口构造函数

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)

throws BeansException {

//调用父类构造方法,为容器设置资源加载器(resourceLoader)

super(parent);

//调用父类AbstractRefreshableConfigApplicationContext的方法,设置//Bean定义的资源文件,完成IoC容器Bean定义资源的定位

setConfigLocations(configLocations);

if (refresh) {

//调用父类AbstractApplicationContext的refresh()

//函数启动载入Bean定义的过程,是Ioc容器载入Bean定义的入口

refresh();

}

}

Spring IoC容器对Bean定义资源的载入是从refresh()函数开始的,FileSystemXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh()函数启动整个IoC容器对Bean定义的载入过程。

3.AbstractApplicationContext的refresh函数载入Bean定义过程:

public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识

prepareRefresh();

//告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从

//子类的refreshBeanFactory()方法启动

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

//为BeanFactory配置容器特性,例如类加载器、事件处理器等

prepareBeanFactory(beanFactory);

try {

//为容器的某些子类指定特殊的BeanPost事件处理器

postProcessBeanFactory(beanFactory);

//调用所有注册的BeanFactoryPostProcessor的Bean

invokeBeanFactoryPostProcessors(beanFactory);

//为BeanFactory注册BeanPost事件处理器.

//BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件

registerBeanPostProcessors(beanFactory);

//初始化信息源,和国际化相关.

initMessageSource();

//初始化容器事件传播器.

initApplicationEventMulticaster();

//调用子类的某些特殊Bean初始化方法

onRefresh();

//为事件传播器注册事件监听器.

registerListeners();

//初始化所有剩余的单态Bean.

finishBeanFactoryInitialization(beanFactory);

//初始化容器的生命周期事件处理器,并发布容器的生命周期事件

finishRefresh();

}

catch (BeansException ex) {

//销毁以创建的单态Bean

destroyBeans();

//取消refresh操作,重置容器的同步标识.

cancelRefresh(ex);

throw ex;

}

}

}

refresh()方法主要为IoC容器Bean的生命周期管理提供条件,Spring IoC容器载入Bean定义资源文件从其子类容器的refreshBeanFactory()方法启动,所以整个refresh()中“ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();”这句以后代码的都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。

AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程,代码如下:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {

//这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory()方法,具//体实现调用子类容器的refreshBeanFactory()方法

refreshBeanFactory();

ConfigurableListableBeanFactory beanFactory = getBeanFactory();

if (logger.isDebugEnabled()) {

logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);

}

return beanFactory;

}

4.AbstractApplicationContext子类的refreshBeanFactory()方法:

AbstractApplicationContext类中只抽象定义了refreshBeanFactory()方法,容器真正调用的是其子类AbstractRefreshableApplicationContext实现的refreshBeanFactory()方法,方法的源码如下:

protected final void refreshBeanFactory() throws BeansException {

if (hasBeanFactory()) {//如果已经有容器,销毁容器中的bean,关闭容器

destroyBeans();

closeBeanFactory();

}

try {

//创建IoC容器

DefaultListableBeanFactory beanFactory = createBeanFactory();

beanFactory.setSerializationId(getId());

//对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等

customizeBeanFactory(beanFactory);

//调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定//义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器

loadBeanDefinitions(beanFactory);

synchronized (this.beanFactoryMonitor) {

this.beanFactory = beanFactory;

}

}

catch (IOException ex) {

throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);

}

}

refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。

和refreshBeanFactory方法类似,载入Bean定义的方法loadBeanDefinitions也使用了委派模式,在AbstractRefreshableApplicationContext类中只定义了抽象方法,具体的实现调用子类容器中的方法实现。

5.AbstractRefreshableApplicationContext子类的loadBeanDefinitions方法:

AbstractRefreshableApplicationContext中只定义了抽象的loadBeanDefinitions方法,容器真正调用的是其子类AbstractXmlApplicationContext对该方法的实现,AbstractXmlApplicationContext的主要源码如下:

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {

……

//实现父类抽象的载入Bean定义方法

@Override

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {

//创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器 //中去,容器使用该读取器读取Bean定义资源

XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

//为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的

//祖先父类AbstractApplicationContext继承DefaultResourceLoader,因//此,容器本身也是一个资源加载器

beanDefinitionReader.setResourceLoader(this);

//为Bean读取器设置SAX xml解析器

beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

//当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制

initBeanDefinitionReader(beanDefinitionReader);

//Bean读取器真正实现加载的方法

loadBeanDefinitions(beanDefinitionReader);

}

//Xml Bean读取器加载Bean定义资源

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {

//获取Bean定义资源的定位

Resource[] configResources = getConfigResources();

if (configResources != null) {

//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位

//的Bean定义资源

reader.loadBeanDefinitions(configResources);

}

//如果子类中获取的Bean定义资源定位为空,则获取//FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源

String[] configLocations = getConfigLocations();

if (configLocations != null) {

//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位

//的Bean定义资源

reader.loadBeanDefinitions(configLocations);

}

}

//这里又使用了一个委托模式,调用子类的获取Bean定义资源定位的方法

//该方法在ClassPathXmlApplicationContext中进行实现,对于我们

//举例分析源码的FileSystemXmlApplicationContext没有使用该方法

protected Resource[] getConfigResources() {

return null;

} ……

}

Xml Bean读取器(XmlBeanDefinitionReader)调用其父类AbstractBeanDefinitionReader的 reader.loadBeanDefinitions方法读取Bean定义资源。

由于我们使用FileSystemXmlApplicationContext作为例子分析,因此getConfigResources的返回值为null,因此程序执行reader.loadBeanDefinitions(configLocations)分支。

6. AbstractBeanDefinitionReader读取Bean定义资源:

AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:

//重载方法,调用下面的loadBeanDefinitions(String, Set<Resource>);方法

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {

return loadBeanDefinitions(location, null);

}

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {

//获取在IoC容器初始化过程中设置的资源加载器

ResourceLoader resourceLoader = getResourceLoader();

if (resourceLoader == null) {

throw new BeanDefinitionStoreException(

"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");

}

if (resourceLoader instanceof ResourcePatternResolver) {

try {

//将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源

//加载多个指定位置的Bean定义资源文件

Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

//委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能

int loadCount = loadBeanDefinitions(resources);

if (actualResources != null) {

for (Resource resource : resources) {

actualResources.add(resource);

}

}

if (logger.isDebugEnabled()) {

logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");

}

return loadCount;

}

catch (IOException ex) {

throw new BeanDefinitionStoreException(

"Could not resolve bean definition resource pattern [" + location + "]", ex);

}

}

else {

//将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源

//加载单个指定位置的Bean定义资源文件

Resource resource = resourceLoader.getResource(location);

//委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能

int loadCount = loadBeanDefinitions(resource);

if (actualResources != null) {

actualResources.add(resource);

}

if (logger.isDebugEnabled()) {

logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");

}

return loadCount;

}

}

//重载方法,调用loadBeanDefinitions(String);

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {

Assert.notNull(locations, "Location array must not be null");

int counter = 0;

for (String location : locations) {

counter += loadBeanDefinitions(location);

}

return counter;

}

loadBeanDefinitions(Resource...resources)方法和上面分析的3个方法类似,同样也是调用XmlBeanDefinitionReader的loadBeanDefinitions方法。

从对AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:

首先,调用资源加载器的获取资源方法resourceLoader.getResource(location),获取到要加载的资源。

其次,真正执行加载功能是其子类XmlBeanDefinitionReader的loadBeanDefinitions方法。

7.资源加载器获取要读入的资源:

XmlBeanDefinitionReader通过调用其父类DefaultResourceLoader的getResource方法获取要加载的资源,其源码如下:

//获取Resource的具体实现方法

public Resource getResource(String location) {

Assert.notNull(location, "Location must not be null");

//这里除了带有classpath标识的Resource

if (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定位,则调用

//容器本身的getResourceByPath方法获取Resource

return getResourceByPath(location);

}

}

}

FileSystemXmlApplicationContext容器提供了getResourceByPath方法的实现,就是为了处理既不是classpath标识,又不是URL标识的Resource定位这种情况。

现在,Bean定义的Resource得到了,下面我们继续跟随程序执行方向,分析XmlBeanDefinitionReader的loadBeanDefinitions方法。

8. XmlBeanDefinitionReader加载Bean定义资源:

//XmlBeanDefinitionReader加载资源的入口方法

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {

//将读入的XML资源进行特殊编码处理

return loadBeanDefinitions(new EncodedResource(resource));

}

//这里是载入XML形式Bean定义资源文件方法

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 cyclic loading of " + encodedResource + " - check your import definitions!");

}

try {

//将资源文件转换为IO输入流

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.remove();

}

}

}

//从特定XML文件中实际载入Bean定义资源的方法

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)

throws BeanDefinitionStoreException {

try {

int validationMode = getValidationModeForResource(resource);

//将XML文件转换为DOM对象,解析过程由documentLoader实现

Document doc = this.documentLoader.loadDocument(

inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());

//这里是启动对Bean定义解析的详细过程,该解析过程会用到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);

}

}

 通过源码分析,载入Bean定义资源文件的最后一步是将Bean定义资源转换为Document对象,该过程由documentLoader实现。

9. DocumentLoader将Bean定义资源转换为Document对象:

DocumentLoader将Bean定义资源转换成Document对象的源码如下:

//使用标准的JAXP将载入的Bean定义资源转换成document对象

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,

ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

//创建文件解析器工厂

DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);

if (logger.isDebugEnabled()) {

logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");

}

//创建文档解析器

DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);

//解析Spring的Bean定义资源

return builder.parse(inputSource);

}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)

throws ParserConfigurationException {

//创建文档解析工厂

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

factory.setNamespaceAware(namespaceAware);

//设置解析XML的校验

if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {

factory.setValidating(true);

if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {

factory.setNamespaceAware(true);

try {

factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);

}

catch (IllegalArgumentException ex) {

ParserConfigurationException pcex = new ParserConfigurationException(

"Unable to validate using XSD: Your JAXP provider [" + factory +

"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +

"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");

pcex.initCause(ex);

throw pcex;

}

}

}

return factory;

}

该解析过程调用JavaEE标准的JAXP标准进行处理。

至此Spring IoC容器根据定位的Bean定义资源文件,将其加载读入并转换成为Document对象过程完成。

10.看源代码的个人心得总结:

通过这几天看源码,个人总结一些心得:代码毕竟不是文章,不能从头到尾详细看,个人觉得看源码比较好的方法是:

首先,先实现一个简单例子调用源码,让整个应用能简单跑起来。

然后,在进入代码最开始的地方打一个调试断点,使用Debug工具进行单步调试,直到跑完所有流程。

通过调试就可以理解整个代码的工作流程和调用顺序,有助于理清思路,理解其大概的设计思想。

Spring代码的确实比较复杂,代码中大量使用了设计模式,另外为了解耦合,代码的分工比较明确,对象也非常的多,对于没有分析源码经验的人来说,阅读代码发现其跨度和跳转非常大,难度和挑战比较大。

我也是第一次分析Spring源码,希望和大家一起学习探讨。