本文的内容是读郝佳的《Spring源码深度解析》整理的笔记。
书中通过一个最简单入门实例,对配置文件的加载、配置文件中标签的解析、bean的加载部分的源码进行了剖析。书中的例子是基于XmlBeanFactory
来创建BeanFactory
,但是这个在类在Spring5.0.x
中已经过时,这里对该实例稍作改动,基于Spring5.0.x
实现。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>5.0.15.RELEASEversion>
dependency>
public class MyBean {
private String testStr= "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}
<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="MyBean" scope="prototype"/>
beans>
public class TestMyBean {
@Test
public void testSimpleLoad(){
Resource resource = new ClassPathResource("spring-config.xml");
BeanFactory bf = new DefaultListableBeanFactory();
BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) bf);
bdr.loadBeanDefinitions(resource);
MyBean myBean = (MyBean) bf.getBean("myTestBean");
System.out.println(myBean.getTestStr());
}
}
控制台打印了testStr
上面代码做了哪些事:
spring-config.xml
配置文件。BeanFactory
。配置文件的作用是记录bean信息,以及创建bean时的一些逻辑。所以Spring必然会有一些代码是用来处理配置文件的。因为配置文件的来源可能有很多种方式,例如:引用资源(在web项目resource根目录)
、classpath:spring-config.xml(类的加载路径)
、file:C:/src/spring-config.xml(文件路径)
等。
Spring将配置文件的一些属性封装为Resource
,包括:文件是否存在、是否可读、是否是Open状态、是否是文件;以及转化为URL、URI、File的方法;还有一些文件属性的获取。
URL、URI、File分别是Java中对url、uri、文件对应的对象。(Java将不同来源的资源抽象成URL,通过注册不同的URLStreamHandler来处理不同来源的资源读取逻辑,handler类型的选择根据不同的前缀(协议,Protocol)来识别)
Resource源码:(点击放大)
InputStreamSource源码:(点击放大)上面Resource类继承了InputStreamSource接口,InputStreamSource源码如下:
可以通过下面这种方式获取InputStream对象
Resource resource = new ClassPathResource("spring-config.xml");
InputStream in = resource.getInputStream();
获取到配置资源后,Spring将读取配置文件,并将spring-config.xml文件中的内容封装为Document
类。在前面的实例中,这个过程在bdr.loadBeanDefinitions(resource);
方法中体现。
深入该方法后,走到XmlBeanDefinitionReader类,loadBeanDefinitions(Resource resource)方法如下。
可以看到loadBeanDefinitions中调用了该类下的同名方法,调用方法前将Resource
封装为EncodedResource
类,可以推断这个方法是用来给文件做编码处理的,该类中的主要逻辑体现在getReader()
方法,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。
loadBeanDefinitions(EncodedResource encodedResource)方法细节:(点击放大)
该方法中最终返回的是doLoadBeanDefinitions(inputSource, encodedResource.getResource())
执行的返回结果,doLoadBeanDefinitions方法中传入两个参数,其中第一个参数inputSource
是Resource对应的InputStream封装得到,目的是想要通过SAX读取XML文件的方式来准备InputSource对象。真正处理的方法是doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法。
doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法细节:(点击放大)
到了这个方法后,终于见到了Document
类。继续跟踪doLoadDocument
方法。
下面分别解释this.documentLoader.loadDocument
方法中的参数:
this.documentLoader.loadDocument方法主要做的就是将xml文件封装为Document
对象,细节都是解析的过程。
下面回到doLoadDocument这里,继续看registerBeanDefinitions方法:该方法主要用来提取bean和注册bean。
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
registerBeanDefinitions(doc, resource)
方法:
同名方法documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
:
继续跟踪doRegisterBeanDefinitions(root)
方法:
preProcessXml(root)
和postProcessXml(root)
方法是空方法,这两个方法是为子类设计的,这是模板设计模式,如果继承DefaultBeanDefinitionDocumentReader
的子类需要在Bean解析前后做一些处理的话,那么只需要重写这两个方法就可以了。
处理完profile标签以后就要进行xml文件的读取了,进入parseBeanDefinitions(root, this.delegate)
方法中:
Spring的xml配置里面有两大类Bean声明,一个是默认标签,例如:
,另外一种是自定义标签,例如:
。
在介绍Spring默认标签解析之前,先介绍一下Spring中常用的一些标签。后面再介绍关于这些标签的解析。