目录

  • 1. applicationContext.xml被封装成一个ClassPathResource类型的对象
  • 2. XmlBeanDefinitionReader
  • 2.1EncodedResource的作用
  • 2.2 SAX方式得到Document
  • 2.3 根据Document得到BeanDefinition
  • 3. Spring源码解析步骤总结



Spring有很多功能,其中最基本的功能就是容器。我们可以把项目中的类使用标签,让我们的类所对应的对象变成一个bean,注入到spring容器中。我们自己的对象变成了一粒一粒的大豆(bean),放在spring容器里,spring 容器管理着黄豆与黄豆之间的关系。我们可以通过配置文件即xml文件,来定义一粒一粒的大豆即bean标签,那spring的源码是如何读取bean标签的呢?这就是本文要探讨的问题。


如果让咱们自己编写一个读取配置文件的代码,我想有几个问题是这个必须要考虑的。


1、 既然是读取文件,那一定需要把配置文件转换成InputStream,但是配置文件存放的位置可以是我们的项目的classpath下,也可以是存放在磁盘的文件系统中,也可以存在远程一台服务器上,针对每一个不同的存放位置我们是不是需要封装得到InputStream的类?


2、 我们的配置文件说白了就是一个xml文件,java API是如何读取xml文件的?


3、 假如我们读取到了一个标签的所有属性了,如何保存?是不是需要封装一个对象来专门保存?


下面看看强大的Spring是如何解决上面的三个问题的。

1. applicationContext.xml被封装成一个ClassPathResource类型的对象

我选用了org.springframework.beans.factory.xml.XmlBeanFactory作为Spring 读取配置文件源码分析的入口,虽然这个类已经被标记为废弃,但是它的代码很少,作为入口很合适。现在我们用一个小Demo作为一切的切入口,看一看Spring的源码设计。
我们有一个MyTest类

public class MyTest {
	private String testStr = "testStr";
	public String getTestStr() {
		return testStr;
	}
	public void setTestStr(String testStr) {
		this.testStr = testStr;
	}
}

我们需要一个MyTest类型的Bean,那么就在配置文件applicationContext.xml文件新增一个bean配置,如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <bean id="myTest" class="com.demo.bean.MyTest"/>
</beans>

那么我们将这个bean放到Spring容器中,并获取一下这个bean,代码如下

public class TestDemo {
    public static void main(String[] args) {
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        MyTest myTest = bf.getBean("myTest");
        System.out.println(myTest.getTestStr());
    }
}

new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); 一行代码就创建了一个类型是XmlBeanFactory的Spring容器,并且我们向容器里注入了一个名叫myTest的bean。我们第一步是以applicationContext.xml为入参,创建了一个ClassPathResource对象。我们最根本的目的是得到InputStream,Spring把我们的配置文件抽象成一个资源Resource,而且Resource最基本的方法就是getInputStream()来满足我们的目的。基于这一点,Spring源码中定义了两个重要的接口,源码如下

package org.springframework.core.io;

import java.io.IOException;
import java.io.InputStream;

public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}

InputStreamSource接口用来满足我们的最基本需求得到inputStream,所以单独抽一个接口出来,Spring的开发者们觉得满足最基本的需要不够过瘾,还可以扩展一下,于是便有了它的子接口Resource接口。

package org.springframework.core.io;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;

public interface Resource extends InputStreamSource {
    boolean exists();
    boolean isReadable();
    boolean isOpen();
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    Resource createRelative(String var1) throws IOException;
    String getFilename();
    String getDescription();
}

Resource 接口是抽象了所有Spring 内部使用到的底层资源: File 、URL 、Classpath 等共有的操作。

  • 它定义了3 个判断当前资源状态的方法:存在性( exists )、可读性( isReadable )、是否处于打开状态( isOpen )。
  • Resource 接口还提供了不同资源到URL 、URI、File 类型的转换,以及获取lastModified属性、文件名(不带路径信息的文件名, getFilename())的方法。
  • Resource 还提供了基于当前资源创建一个相对资源的方法: createRelative() 。
  • 在错误处理中需要详细地打印出锚的资源文件,因而Resource 还提供了getDescription()方法用来在错误处理中打印信息。
    对不同来源的配置文件都有相应的Resource 实现:
    文件系统(FileSystemResource) 、项目的Classpath资源(ClassPathResource)、
    URL资源(UrlResource)、InputStream 资源(InputStreamResource) 、
    Byte 数组(ByteArrayResource)等。
    这样Spring抽象出这两个接口,并根据不同的实现类来得到inputStream,便解决了我们的第一个问题!那我们现在看看ClassPathResource的源码。
package org.springframework.core.io;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

public class ClassPathResource extends AbstractFileResolvingResource {
    //classPath下的绝对路径
private final String path;
//优先使用类加载器用于得到inputStream
private ClassLoader classLoader;
//其次使用Class类对象得到inputStream
    private Class<?> clazz;
    
    // 我们使用的方式,得到path和classLoader,来得到inputStream
    public ClassPathResource(String path) {
        this(path, (ClassLoader)null);
    }

    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }

        this.path = pathToUse;
        this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
    }

    public ClassPathResource(String path, Class<?> clazz) {
        Assert.notNull(path, "Path must not be null");
        this.path = StringUtils.cleanPath(path);
        this.clazz = clazz;
    }

    protected ClassPathResource(String path, ClassLoader classLoader, Class<?> clazz) {
        this.path = StringUtils.cleanPath(path);
        this.classLoader = classLoader;
        this.clazz = clazz;
    }

    public final String getPath() {
        return this.path;
    }

    public final ClassLoader getClassLoader() {
        return this.clazz != null ? this.clazz.getClassLoader() : this.classLoader;
    }
    public boolean exists() {
        return this.resolveURL() != null;
    }
    protected URL resolveURL() {
        if (this.clazz != null) {
            return this.clazz.getResource(this.path);
        } else {
            return this.classLoader != null ? this.classLoader.getResource(this.path) : ClassLoader.getSystemResource(this.path);
        }
    }
    //使用类加载器的getResourceAsStream方法得到inputStream
    public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        } else if (this.classLoader != null) {
            is = this.classLoader.getResourceAsStream(this.path);
        } else {
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }

        if (is == null) {
            throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
        } else {
            return is;
        }
    }

    public URL getURL() throws IOException {
        URL url = this.resolveURL();
        if (url == null) {
            throw new FileNotFoundException(this.getDescription() + " cannot be resolved to URL because it does not exist");
        } else {
            return url;
        }
    }

    public Resource createRelative(String relativePath) {
        String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
        return this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) : new ClassPathResource(pathToUse, this.classLoader);
    }

    public String getFilename() {
        return StringUtils.getFilename(this.path);
    }

    public String getDescription() {
        StringBuilder builder = new StringBuilder("class path resource [");
        String pathToUse = this.path;
        if (this.clazz != null && !pathToUse.startsWith("/")) {
            builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
            builder.append('/');
        }

        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }

        builder.append(pathToUse);
        builder.append(']');
        return builder.toString();
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof ClassPathResource)) {
            return false;
        } else {
            ClassPathResource otherRes = (ClassPathResource)obj;
            return this.path.equals(otherRes.path) && ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) && ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz);
        }
    }

    public int hashCode() {
        return this.path.hashCode();
    }
}

分析到这里,我真是由衷的佩服,竟然使用类加载器去得到inputStream!
什么是类加载器?简单点说,就是负责把class文件加载进内存中,并创建一个java.lang.Class类的一个实例,也就是class对象;getResourceAsStream(path)是用来获取资源的,类加载器默认是从classPath下获取资源的,因为这下面有class文件。所以这段代码总的意思是通过类加载器在classPath目录下获取资源.并且是以流的形式返回。
到这里咱们就搞清楚了Spring是如何封装咱们的classPath下的配置文件,如何得到相应的inputStream,下面看看Spring根据这个能得到inputStream的ClassPathResource又做了什么事。

2. XmlBeanDefinitionReader

package org.springframework.beans.factory.xml;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.io.Resource;

/** @deprecated */
@Deprecated
public class XmlBeanFactory extends DefaultListableBeanFactory {
    private final XmlBeanDefinitionReader reader;
    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);
    }
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);
    }
}

XmlBeanFactory 继承自DefaultListableBeanFactory ,而DefaultListableBeanFactmy 是整个bean加载的核心部分,是Spring 注册及加载bean的默认实现。根据源码看XmlBeanFactory 只是比DefaultListableBeanFactory多了一个类型是
XmlBeanDefinitionReader的属性而已,并在构造方法里面初始化了这个属性,然后用这个
XML文件读取器XmlBeanDefinitionReader去解析InputStream得到BeanDefinition。
这个XmlBeanDefinitionReader很容易理解将解析InputStream的代码封装到一起。至于父类DefaultListableBeanFactory的构造方法调用以及它的源码分析,这里留一个坑,以后有机会再填上吧(原谅我这个下班后辛苦码字的小伙子吧)。因为XmlBeanDefinitionReader的源码内容比较多,这里我们就先欣赏一下它的loadBeanDefinitions(Resource resource)方法。

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource)
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(new EncodedResource(resource));
}
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);
	}
	//通过属性来记录已经加载的资源
	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
	if (currentResources == null) {
		currentResources = new HashSet<>(4);
		this.resourcesCurrentlyBeingLoaded.set(currentResources);
	}
	if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}
	try {
		//从encodeResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
			//InputSource 这个类并不来自于Spring,它的全路径是org.xml.sax
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			//通过SAX读取xml文件,所以传入inputSource,同时也将Resource传进去用来获取xml的验证模式是XSD还是DTD,进行真正的xml文件内容解析读取
			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();
		}
	}
}
  1. ThreadLocal<Set> resourcesCurrentlyBeingLoaded 属性的作用
  2. EncodedResource的作用
  3. 准备java类库中的InputSource,用于解析xml文件

2.1EncodedResource的作用

从上面的源码看第一步创建了一个EncodedResource对象,这是为什么呢?从前面的分析我们的配置文件变成了一个ClassPathResource,通过ClassPathResource的getInputStream()方法我们就能得到一个字节流了,那如果我想转换成一个特定编码的字符流呢?我贴一下它源码,咱们看看它作用是什么

package org.springframework.core.io.support;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;

import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 *将{@link resource}与特定编码或者{@code charset}组合在一起的持有者用于读取资源。
 *用作读取具有特定编码参数的内容。
 */
public class EncodedResource implements InputStreamSource {
	/** 需要读取的资源*/
	private final Resource resource;
	/** 读取资源时需要设定的编码*/
	@Nullable
	private final String encoding;
	/** 读取资源时需要设定的编码*/
	@Nullable
	private final Charset charset;

    /**
	 *构造方法
	*/
	public EncodedResource(Resource resource) {
		this(resource, null, null);
	}
	public EncodedResource(Resource resource, @Nullable String encoding) {
		this(resource, encoding, null);
	}
	public EncodedResource(Resource resource, @Nullable Charset charset) {
		this(resource, null, charset);
	}
	private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
		super();
		Assert.notNull(resource, "Resource must not be null");
		this.resource = resource;
		this.encoding = encoding;
		this.charset = charset;
	}

    /**
	 *get方法
	 */
	public final Resource getResource() {
		return this.resource;
	}
	@Nullable
	public final String getEncoding() {
		return this.encoding;
	}
	@Nullable
	public final Charset getCharset() {
		return this.charset;
	}

	/**
	 * 判断是否设置编码
	 */
	public boolean requiresReader() {
		return (this.encoding != null || this.charset != null);
	}

	/**
	 * 使用给定的charset或者encoding编码,将通过resource的getInputStrem()方法得到的字节流转换成字符流
	 */
	public Reader getReader() throws IOException {
		if (this.charset != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.charset);
		}
		else if (this.encoding != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.encoding);
		}
		else {
			return new InputStreamReader(this.resource.getInputStream());
		}
	}

	/**
	 * 重写最基本的接口InputStreamSource的getInputStream方法
	 * 因为resource就是我们想要解析的配置文件,那么直接调用resource的getInputStream方法就能得到inputStream!
	 */
	@Override
	public InputStream getInputStream() throws IOException {
		return this.resource.getInputStream();
	}


	@Override
	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}
		if (!(other instanceof EncodedResource)) {
			return false;
		}
		EncodedResource otherResource = (EncodedResource) other;
		return (this.resource.equals(otherResource.resource) &&
				ObjectUtils.nullSafeEquals(this.charset, otherResource.charset) &&
				ObjectUtils.nullSafeEquals(this.encoding, otherResource.encoding));
	}
	@Override
	public int hashCode() {
		return this.resource.hashCode();
	}
	@Override
	public String toString() {
		return this.resource.toString();
	}
}

Spring设计这个类的目的是把它当作一个持有者Holder的角色,它封装了我们的资源和编码,提供了得到对应资源的inputStream方法getInputStream()方法和字节流inputStream转换成字符流转换的方法getReader()方法。只不过在创建这个EncodedResource对象时,没有指定编码,在后面的源码分析中也没有用到getReader()方法做字符流转换,只用到了getInputStream()这一个方法。总得来说,设计思想很美好,只不过这里没有用到而已!搞清楚这个对象以后,咱们看看下面的第二个loadBeanDefinitions 方法。为了方便说明,我再贴一下它的源码。

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);
	}
	//通过属性来记录正在加载的资源
	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
	if (currentResources == null) {
		currentResources = new HashSet<>(4);
		this.resourcesCurrentlyBeingLoaded.set(currentResources);
	}
	if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}
	try {
		//从encodeResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
			//InputSource 这个类并不来自于Spring,它的全路径是org.xml.sax
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			//通过SAX读取xml文件,所以传入inputSource,同时也将Resource传进去用来获取xml的验证模式是XSD还是DTD,进行真正的xml文件内容解析读取
			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();
		}
	}

按照前边我的铺垫,这个方法真的很容易理解了

  1. 记录正在解析的资源,存放在resourcesCurrentlyBeingLoaded属性中。
  2. 通过encodedResource得到inputStream流。
  3. java解析xml有四种方式,Spring在这里选择SAX的方式去解析xml文档,所以使用inputStream创建了SAX方式必须的inputSource。

最后将inputSource和classPathResource又传给了doLoadBeanDefinitions方法。EncodedResource被抛弃掉了啊,虽然上面的方法也没有用EncodedResource单独提供的功能。到这里就已经解答了我在开头的第二个问题!doLoadBeanDefinitions方法该干什么?既然已经执行到这里了,那就简单了,使用inputSource得到Document,然后解析Document得到配置文件里面的内容。

2.2 SAX方式得到Document

我们先看看doLoadBeanDefinitions方法的源码是不是和我想的一样。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
	try {
		//1. 通过SAX读取xml文件,所以传入inputSource,同时也将Resource传进去,进行真正的xml文件内容解析读取
		//最终将xml文件转换成Document
		Document doc = doLoadDocument(inputSource, resource);
		//2. 根据Document注册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);
	}
}

果然如此,两个功能分别对应两个方法。先看一下得到Document的方法doLoadDocument

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
	// 此方法下面的方法就是获取xml验证模式的方法getValidationModeForResource,判断配置文件是否包含DOCTYPE,如果包含就是DTD,否则就是XSD
	//documentLoader.loadDocument方法不需要在使用Resource了,只需要使用有Resource转换的inputSource就够了
	return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
			getValidationModeForResource(resource), isNamespaceAware());
}

Spring利用java的封装特性在这里又一次的得到体现。我们想做的是根据inputSource得到Document,这是java API提供的功能,所以Spring将这一块的代码封装到了
DefaultDocumentLoader类里面,为了使用它单独生命了一个此类型的属性。这让我想起了设计模式中的迪米特原则和单一职责。至于Spring是如何运用SAX的底层api来得到Document,这里就再留一个坑吧,因为这个就只是SAX解析xml的知识点了。

2.3 根据Document得到BeanDefinition

咱们来看看第二步,Spring得到这个配置文件对应的document对象,都做了什么吧,这是重点!

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	// 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader,此方法的下面的一个方法
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	// 解析doc前BeanDefinition的个数
	int countBefore = getRegistry().getBeanDefinitionCount();
	// 加载及注册bean!!!!
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	// 返回本次加载的BeanDefinition的个数
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

熟悉XML解析的码农,应该知道这个Document里面封装的是Root,Element这些内容,也就对应了我们的配置文件的、,那么把这些东西从Document取出来的逻辑Spring是如何实现的呢?这里Spring使用java反射机制创建了名叫,一个用于从Document取出Root、Element的DefaultBeanDefinitionDocumentReader类型的变量documentReader。将解析的逻辑封装到这个类的registerBeanDefinitions方法里,这个方法的入参有一个XmlReaderContext类型的入参,就是初始化了一个上下文对象,目前看没啥作用。

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	logger.debug("Loading bean definitions");
	// 提取Root
	Element root = doc.getDocumentElement();
	// 真正开始解析Element root,就在此方法的下面第三个方法
	doRegisterBeanDefinitions(root);
}

继续看真正的解析方法doRegisterBeanDefinitions(Element root)

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
	// 专门处理解析
	BeanDefinitionParserDelegate parent = this.delegate;
	this.delegate = createDelegate(getReaderContext(), root, parent);

	// 处理profile属性
	if (this.delegate.isDefaultNamespace(root)) {
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if (StringUtils.hasText(profileSpec)) {
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
					profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
				if (logger.isInfoEnabled()) {
					logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
							"] not matching: " + getReaderContext().getResource());
				}
				return;
			}
		}
	}

	// 解析前处理,留给子类实现
	preProcessXml(root);
	// 解析逻辑,就是此方面下面第二个方法
	parseBeanDefinitions(root, this.delegate);
	// 解析后处理,留给子类实现
	postProcessXml(root);

	this.delegate = parent;
}

犹抱琵琶半遮面,千呼万唤始出来。开始解析我们的Root!在方法的第一行就是创建了一个工具对象BeanDefinitionParserDelegate,将我们的Element转换成BeanDefinition类型的对象!到这里我们就清楚了,Spring会将我们的bean标签、import标签等等标签转换成对应的BeanDefinition类型的对象!其实就是将Bean的定义信息存储到这个BeanDefinition相应的属性中,后面对Bean的操作就直接对BeanDefinition进行,例如拿到这个BeanDefinition后,可以根据里面的类名、构造函数、构造函数参数,使用反射进行对象创建。BeanDefinition是一个接口,是一个抽象的定义,实际使用的是其实现类,如 ChildBeanDefinition、RootBeanDefinition、GenericBeanDefinition等。下面看一下如何使用BeanDefinition转换工具,进行转换。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	if (delegate.isDefaultNamespace(root)) {
		NodeList nl = root.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element) {
				Element ele = (Element) node;
				if (delegate.isDefaultNamespace(ele)) {
					// 默认标签bean标签等的解析
					parseDefaultElement(ele, delegate);
				}
				else {
					// 自定标签<tx:annotation-driven/>的解析
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}

使用delegate判断是默认标签bean,还是自定标签等,这个工具类也是设计模式体现,要是咱们写会怎么样呢?再往下就是使用工具类解析Element得到Beandefinition的逻辑了,已经和咱们的配置文件没有关系了,这块的源码分析,我得再开一个坑。

3. Spring源码解析步骤总结

  1. 创建ClassPathResource,用于封装配置文件的classpath路径和getInputStream()方法获取字节流。
  2. 将ClassPathResource和特定编码封装成EncodedResource对象,可以按照特定编码进行字符流转换。
  3. 通过EncodedResource的里面的ClassPathResource的getInputStream()方法获取字节流,再由字节流获取InputSource,将这两个变量交给DefaultDocumentLoader,进行解析得到Document。
  4. Document再交给XmlBeanDefinitionReader进行遍历,将得到我们需要的BeanDefinition。
    分析到这里,回到最初。这还在
    new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));这一行的里面呢!这真是不得不佩服Spring的封装性,对设计思想的灵活使用。