目录
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();
}
}
}
- ThreadLocal<Set> resourcesCurrentlyBeingLoaded 属性的作用
- EncodedResource的作用
- 准备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();
}
}
按照前边我的铺垫,这个方法真的很容易理解了
- 记录正在解析的资源,存放在resourcesCurrentlyBeingLoaded属性中。
- 通过encodedResource得到inputStream流。
- 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源码解析步骤总结
- 创建ClassPathResource,用于封装配置文件的classpath路径和getInputStream()方法获取字节流。
- 将ClassPathResource和特定编码封装成EncodedResource对象,可以按照特定编码进行字符流转换。
- 通过EncodedResource的里面的ClassPathResource的getInputStream()方法获取字节流,再由字节流获取InputSource,将这两个变量交给DefaultDocumentLoader,进行解析得到Document。
- Document再交给XmlBeanDefinitionReader进行遍历,将得到我们需要的BeanDefinition。
分析到这里,回到最初。这还在
new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
这一行的里面呢!这真是不得不佩服Spring的封装性,对设计思想的灵活使用。