源代码计划: Spring 源码深度解析

源代码计划: Spring 源码深度解析

前言

​ 本篇以 Spring 源码深度解析 一书为主线 , 根据书中对 spring 源码研究并复现书中的 spring 原理的流程图与时序图 . 同时在 Spring 源码当中其设计理念由于高度的可扩展性, 源码当中有大量的关于设计模式的思想,对于设计模式笔者会同时更新一份关于 C++ 实现设计模式的随笔记录 - Rewrite Design 23 Patterns . 那么就让我们 Start a new journey together !

本篇记录的源代码地址: spring-framework

容器的基本实现

容器的基本用法

  • 定义一个 bean
java 复制代码
public class MyTestBean {
	private String testStr="testStr";

	public String getTestStr(){
		return testStr;
	}
	public void setTestStr(String testStr){
		this.testStr=testStr;
	}
}
  • 创建 xml 配置文件
XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans 
	   http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<bean id="myTestBean" class="com.peppa.bean.MyTestBean">
		<property name="testStr" value="特兰克斯"/>
	</bean>
	
</beans>
  • 编写测试启动类
java 复制代码
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
	@Test
	public void testSimpleLoad(){
		BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
		MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
		System.out.println(myTestBean.getTestStr());
	}
}

输出结果:

Task :spring-debug:test

特兰克斯

功能研究分析

​ 完成上面的代码流程,可以思考一下。如果自己是设计者那么自己会如何进行设计呢?笔者觉得在编码当中思想的实现比代码更加重要, 而快速学会一套原理往往站在设计者的角度去考虑,效率会更高一些。先有一个宏观的认识,在意识当中有一个清晰的框架,再去研究细节,这样才会更加的得心应手,就像太极拳法与伏羲八卦一样,一生万物。

​ 从上面的步骤可以看到,分别做了创建了一个 POJO ,在 xml 配置文件中指定 POJO,让 BeanFactory 加载 xml,获取容器中的 bean ,打印 bean 内容,这几步骤。

但实际上关键核心的步骤应该是:

  1. 读取 XML 配置文件
  2. 解析 XML 配置文件
  3. 实例化指定的 Bean

​ 所以,如果想要实现一个特别简单的 spring 加载机制其实就只需要按照下图的 UML 类就可以设计出一份。但实际上,Spring 的设计比这要复杂抽象的多,每一步当中的都附加类很多繁杂的细节以供后续的扩展。

Spring 的结构组成

核心类介绍

​ 在 Spring 当中核心的两个类,DefaultListableBeanFactoryXmlBeanDefinitionReader

​ *注 作者书中采用的是 spring 3.2x 版本 与笔者使用的 spring 6.5x 版本 在一些具体的实现方面存在不同,但核心逻辑底层是不会变动的。

  1. DefaultListableBeanFactory 用于 bean 的创建管理
  1. XmlBeanDefinitionReader 用于配置文件的读取解析

加载 Bean

java 复制代码
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
java 复制代码
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader.loadBeanDefinitions(resource);
}

​ XmlBeanFactory 构造函数中调用了 XmlBeanDefinitionReader 类型的 reader 属性当中提供的 this.reader.loadBeanDefinitions(resource) , 而 这一段代码是整个资源加载的切入点。该方法完成了对 xml 文件的整个加载与解析以及 bean 对象构建。

java 复制代码
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}
java 复制代码
    try (InputStream inputStream = encodedResource.getResource().getInputStream()) { // 获取文件输入流
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
java 复制代码
InputStream is;
is = this.classLoader.getResourceAsStream(this.path);
java 复制代码
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

​ 现在已经将 xml 配置文件加载到了 InputSource 对象当中,最后将准备的数据参数传入真正的核心处理部分 doLoadBeanDefinitions(inputSource, encodedResource.getResource())

java 复制代码
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {

    try {
        //此处获取 xml 文件的 document 对象,这个解析过程是由 documentLoader 完成的
        Document doc = doLoadDocument(inputSource, resource);
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
    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);
    }
}

在上面的代码中只做了两件事

  1. doLoadDocument(inputSource, resource);

    将 IputSources 的配置文件对象资源解析转化成对应的 Document 对象

  2. registerBeanDefinitions(doc, resource);

    根据返回的 Document 对象注册 Bean 信息

​ 以上的这两个步骤支撑着整个 Spring 容器部分的实现基础。尤其是 registerBeanDefinitions 对配置文件的解析以及 bean 的信息注册,逻辑非常的复杂。

获取 XML 的验证模式

​ 对于 XML 配置文件 ,了解的读者都知道 XML 文件的验证模式保证了 XML 文件的正确性,而比较常用的验证模式有两种:DTDXSD

​ 关于 DTD 与 XSD 这里就不赘述读者可以参考:W3Cschool-DTD 文档教程 W3C school-XSD文档教程

java 复制代码
protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = getValidationMode();
    if (validationModeToUse != VALIDATION_AUTO) {  // 如果手动指定,则使用手动指定的模式
        return validationModeToUse;
    }
    int detectedMode = detectValidationMode(resource); // 未使用则自动检测
    if (detectedMode != VALIDATION_AUTO) {
        return detectedMode;
    }
    // Hmm, we didn't get a clear indication... Let's assume XSD,
    // since apparently no DTD declaration has been found up until
    // detection stopped (before finding the document's root tag).
    return VALIDATION_XSD;
}

可以通过调用 XmlBeanDefinitionReader 当中的 setValidationMode 进行设定,指定的模式。

java 复制代码
public void setValidationMode(int validationMode) {
    this.validationMode = validationMode;
}
java 复制代码
public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;

public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;

private int validationMode = VALIDATION_AUTO;

​ 否则使用自动检测的方式,而对于自动检测验证的模式的功能是在函数 detectValidationMode 中实现的,在 detectValidationMode 函数当中又将自动检测验证模式的工作委托给了专门处理类 XmlValidationModeDetector ,调用 XmlValidationModeDetector 的 detectValidationMode 方法实现。

java 复制代码
private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();


protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
    throw new BeanDefinitionStoreException(
            "Passed-in Resource [" + resource + "] contains an open stream: " +
            "cannot determine validation mode automatically. Either pass in a Resource " +
            "that is able to create fresh streams, or explicitly specify the validationMode " +
            "on your XmlBeanDefinitionReader instance.");
}

InputStream inputStream;
try {
    inputStream = resource.getInputStream();
}
catch (IOException ex) {
    throw new BeanDefinitionStoreException(
            "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
            "Did you attempt to load directly from a SAX InputSource without specifying the " +
            "validationMode on your XmlBeanDefinitionReader instance?", ex);
}

try {
    return this.validationModeDetector.detectValidationMode(inputStream);
}
catch (IOException ex) {
    throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
            resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}

​ 只要理解了 XSD 与 DTD 的使用方法,理解下面的检测验证模式的方法就不会太难,其 String 用来检测验证模式的办法就是判断是否包含 DOCTYPE ,如果包含就是 DTD 否则就是 XSD 。

java 复制代码
public int detectValidationMode(InputStream inputStream) throws IOException {
    // Peek into the file to look for DOCTYPE.
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    try {
        boolean isDtdValidated = false;
        String content;
        while ((content = reader.readLine()) != null) {
            content = consumeCommentTokens(content);
            //如果读取的行是空行或者是注释则跳过
            if (this.inComment || !StringUtils.hasText(content)) {
                continue;
            }
            if (hasDoctype(content)) {
                isDtdValidated = true;
                break;
            }
            //读取到 < 开始符号,验证模式一定会在开始符号之前
            if (hasOpeningTag(content)) {
                // End of meaningful data...
                break;
            }
        }
        return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
    }
    catch (CharConversionException ex) {
        // Choked on some character encoding...
        // Leave the decision up to the caller.
        return VALIDATION_AUTO;
    }
    finally {
        reader.close();
    }
}
java 复制代码
private boolean hasDoctype(String content) {
    return content.contains(DOCTYPE);
}

获取 Document

经过上面的 验证模式 准备工作完成后,就可以进行 Document 加载了,同样的 XmlBeanFactoryReader 类对于文档读取并没自己亲自的去完成,而是委托给了 DocumentLoader 去执行的。

java 复制代码
private DocumentLoader documentLoader = new DefaultDocumentLoader();

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware());
}
java 复制代码
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
        ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isTraceEnabled()) {
        logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    // 至于这里是如何将 XML 文件解析构建成一个 Document 对象的这里就不深究了,想看也可以吃自己去查找一下。
    return builder.parse(inputSource);
}

关于通过 SAX 解析 XML 文档的套路基本都是一致的,而 Spring 这里也并没有什么特殊的地方,通过的也是创建了一个 DocumentBuilderFactory 工厂,再通过工厂创建一个 DocumentBuilder 对象,进行解析 imputSource 来返回 Document 对象。

这里需要注意的一点:

参数 entityResolver 的传入是通过 getEntityResolver()方法获取的函数返回值

代码如下:

java 复制代码
protected EntityResolver getEntityResolver() {
    if (this.entityResolver == null) {
        // Determine default EntityResolver to use.
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader != null) {
            this.entityResolver = new ResourceEntityResolver(resourceLoader);
        }
        else {
            this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
        }
    }
    return this.entityResolver;
}

那么 EntityResolver 到底是做什么用的呢?

EntityResolver 用法

相关推荐
工业互联网专业19 小时前
毕业设计选题:基于ssm+vue+uniapp的校园水电费管理小程序
vue.js·小程序·uni-app·毕业设计·ssm·源码·课程设计
IT研究室5 天前
大数据毕业设计选题推荐-电影数据分析系统-数据可视化-Hive-Hadoop-Spark
大数据·hive·hadoop·spark·毕业设计·源码·课程设计
IT毕设梦工厂5 天前
大数据毕业设计选题推荐-NBA球员数据分析系统-Python数据可视化-Hive-Hadoop-Spark
大数据·hive·hadoop·spark·毕业设计·源码·课程设计
IT研究室5 天前
大数据毕业设计选题推荐-民族服饰数据分析系统-Python数据可视化-Hive-Hadoop-Spark
大数据·hive·hadoop·spark·毕业设计·源码·课程设计
一 乐6 天前
高校体育场小程序|高校体育场管理系统系统|体育场管理系统小程序设计与实现(源码+数据库+文档)
数据库·小程序·vue·源码·springboot·体育馆小程序
工业互联网专业6 天前
毕业设计选题:基于springboot+vue+uniapp的在线办公小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计
一 乐7 天前
畅阅读小程序|畅阅读系统|基于java的畅阅读系统小程序设计与实现(源码+数据库+文档)
java·小程序·vue·源码·springboot·阅读小程序
一 乐7 天前
助农小程序|助农扶贫系统|基于java的助农扶贫系统小程序设计与实现(源码+数据库+文档)
java·数据库·小程序·vue·源码·助农
一 乐7 天前
订餐点餐|订餐系统基于java的订餐点餐系统小程序设计与实现(源码+数据库+文档)
java·数据库·spring boot·后端·源码
Iareges9 天前
PyTorch源码系列(一)——Optimizer源码详解
人工智能·pytorch·python·源码·优化算法·sgd·optimizer