源代码计划: 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 内容,这几步骤。
但实际上关键核心的步骤应该是:
- 读取 XML 配置文件
- 解析 XML 配置文件
- 实例化指定的 Bean
所以,如果想要实现一个特别简单的 spring 加载机制其实就只需要按照下图的 UML 类就可以设计出一份。但实际上,Spring 的设计比这要复杂抽象的多,每一步当中的都附加类很多繁杂的细节以供后续的扩展。
Spring 的结构组成
核心类介绍
在 Spring 当中核心的两个类,DefaultListableBeanFactory
和 XmlBeanDefinitionReader
*注 作者书中采用的是 spring 3.2x 版本
与笔者使用的 spring 6.5x 版本 在一些具体的实现方面存在不同,但核心逻辑底层是不会变动的。
- DefaultListableBeanFactory 用于 bean 的创建管理
- 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);
}
}
在上面的代码中只做了两件事
-
doLoadDocument(inputSource, resource);
将 IputSources 的配置文件对象资源解析转化成对应的 Document 对象
-
registerBeanDefinitions(doc, resource);
根据返回的 Document 对象注册 Bean 信息
以上的这两个步骤支撑着整个 Spring 容器部分的实现基础。尤其是 registerBeanDefinitions
对配置文件的解析以及 bean 的信息注册,逻辑非常的复杂。
获取 XML 的验证模式
对于 XML 配置文件 ,了解的读者都知道 XML 文件的验证模式保证了 XML 文件的正确性,而比较常用的验证模式有两种:DTD
和 XSD
。
关于 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 到底是做什么用的呢?