Spring源码解析-容器的基本实现

时间:2021-05-24 17:18:15

首先来回顾一下简单的bean获取。
1、bean类

public class MyTestBean {
private String name = "whz";

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

2、配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<bean id="myTestBean" class="bean.MyTestBean"></bean>
</beans>

3、测试程序

public class BeanFactoryTest {

@Test
public void testSimpleLoad(){
BeanFactory bf =new XmlBeanFactory(new ClassPathResource(("beanFactoryTest.xml")));
MyTestBean myTestBean = (MyTestBean)bf.getBean("myTestBean");
System.out.println(myTestBean.getName());
}
}

4、结果

whz

通过初步猜想,我们认为是先加载xml配置文件,获取bean中的类名,然后通过反射机制进行实例化。

现在开始分析源码;
①配置文件封装:
首先通过new ClassPathResource((“beanFactoryTest.xml”)对配置文件进行封装,ClassPathResource继承的类实现了Resource接口,可以返回文件输入流,也就是getInputStream()方法。
我们进入XmlBeanFactory的构造方法:

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);
}

这个工厂实例化了一个XmlBeanDefinitionReader类,然后这个类有加载了资源文件,也就是这个配置文件。进入loadBeanDefinitions()方法,

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}

首先看见对resource用EncodedResource进行封装。这个类是用于对资源文件的编码进行处理。
接着进入loadBeanDefinitions方法

InputStream inputStream = encodedResource.getResource().getInputStream();

我们可以看到这么一段代码,可见先是获取配置文件的输入流,

try {
//将输入流封装成InputSource,这个类往往用来读取xml文件
InputSource inputSource = new InputSource(inputStream);
if(encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}

var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}

进入核心部分doLoadBeanDefinitions()方法,我删除了一些异常处理,留下关键代码,可见通过doLoadDocument加载Doucment对象,学过XML读取的都知道这是xml文件中的一种对象,跟html也有相似之处。然后根据这个Document对象注册Bean信息。这个代码是spring4的代码,可能与spring3版本有点不一样,但是大体过程是一样的。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {

Document doc = this.doLoadDocument(inputSource, resource);
return this.registerBeanDefinitions(doc, resource);

}

进入加载doLoadDocument方法。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}

我们能看见有这么一段代码:this.getValidationModeForResource(resource),这段代码是获取xml 的验证模式。了解XML文件的应该知道XML的验证模式,一般有DTD和XSD这两种验证模式,一般会在XML文件的头部加上验证模式的声明,虽然IDE会报错,但是我们在读取xml时还是要验证xml 的格式规范。DTD是一种XML约束模式语言,XSD描述了XML文档的结构。在这对这两种模式就不详细解释了。
首先我们要从xml文件中获取验证模式

protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = this.getValidationMode();
//如果手动制定了验证模式则使用指定的验证模式
if(validationModeToUse != 1) {
return validationModeToUse;
} else {
//否则就自动检测验证模式
int detectedMode = this.detectValidationMode(resource);
return detectedMode != 1?detectedMode:3;
}
}

进入detectValidationMode()方法,删除了一些抛出异常

rotected int detectValidationMode(Resource resource) {
if(resource.isOpen()) {

} else {
InputStream inputStream;

inputStream = resource.getInputStream();
this.validationModeDetector.detectValidationMode(inputStream);

}

继续进入detectValidationMode方法

public int detectValidationMode(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

byte var4;
try {
boolean isDtdValidated = false;

while(true) {
String content;
//如果是空行就略过
if((content = reader.readLine()) != null) {
content = this.consumeCommentTokens(content);
//如果是注释也略过
if(this.inComment || !StringUtils.hasText(content)) {
continue;
}
//如果有DOCTYPE标记就说明是DTD模式
if(this.hasDoctype(content)) {
isDtdValidated = true;
} else if(!this.hasOpeningTag(content)) {
continue;
}
}

int var5 = isDtdValidated?2:3;
return var5;
}
} catch (CharConversionException var9) {
var4 = 1;
} finally {
reader.close();
}

return var4;
}

获取了验证模式后,接下来我们要获取Document对象

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}

进入这个loadDocument方法
发现是接口,然后我们的找到实体类:

 private DocumentLoader documentLoader = new DefaultDocumentLoader();

进入这个类,找到loadDocument方法

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//首先创建document工厂
DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
if(logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
然后创建documentBuilder
DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}

然后用这个builder来解析输入流,这是通过SAX来解析XML文档,在这其中还传入了一个参数entityResolver,我们知道XML文档解析需要获取DTD约束文档或者XSD的结构文档,来对XML文件进行验证,首先获取这个EntityResolver

protected EntityResolver getEntityResolver() {
if(this.entityResolver == null) {
ResourceLoader resourceLoader = this.getResourceLoader();
if(resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
}
}

return this.entityResolver;
}

spring采用DelegatingEntityResolver为EntityResolver实现类,
从代码中看出,不同的验证模式会采用不同的解析器。

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if(systemId != null) {
if(systemId.endsWith(".dtd")) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}

if(systemId.endsWith(".xsd")) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}

return null;
}

SAX首先会读取xml上的声明

例如:xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd

因为从网络上获取DTD声明或者XSD非常不可靠,所以SAX
调用EntityResolver的resolveEntity方法(参数为获取的声明),来从本地获取约束文件,加载DTD类型的BeansDtdResolver的resolveEntity方法是直接截取systemId最后的xx.dtd,然后去当前路径下查找,而加载XSD类型的PluggableSchemaResolver,是默认到META-INF/Spring.schemas文件中找到systemId对应的XSD文件并加载。
我们文件中的一部分,可以看书每个声明对应了不同的xsd文件位置。

http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
http\://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd
http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd

获取到document对象后,
解析及注册BeanDefinitions,接下来是解析及注册BeanDefinitions

 Document doc = this.doLoadDocument(inputSource, resource);
return this.registerBeanDefinitions(doc, resource);

进入registerBeanDefinitions方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//创建BeanDefinitionDocumentReader对象
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
//设置环境变量
documentReader.setEnvironment(this.getEnvironment());
//记录统计前beanDefition的个数
int countBefore = this.getRegistry().getBeanDefinitionCount();
//加载(或者说是解析)及注册bean
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
//记录本次加载的个数
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

BeanDefinitionDocumentReader是个接口,真正的类型是DefaultBeanDefinitionDocumentReade,打开这个类,找到registerBeanDefinitions这个方法。

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
//提取root元素
Element root = doc.getDocumentElement();
this.doRegisterBeanDefinitions(root);
}

进入doRegisterBeanDefinitions查看

protected void doRegisterBeanDefinitions(Element root) {
//处理profile属性
String profileSpec = root.getAttribute("profile");
if(StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if(!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}
//专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.readerContext, root, parent);
//解析前处理,留给子类实现
this.preProcessXml(root);
//解析
this.parseBeanDefinitions(root, this.delegate);
解析后处理,留给子类实现
this.postProcessXml(root);
this.delegate = parent;
}

profile属性我们不常用,
我们可以再beans标签中加入profile属性

例如
<beans profile="aaa"></beans>
<beans profile="bbb"></beans>

集成到web环境中时在web.xml中加入以下代码

<context-param>
<param-name>Spring.profiles.active</param-name>
<param-value>aaa</param-value>
</context-param>

这样我们可以采用两套spirng配置
接着我们来说preProcessXml和postProcessXml方法,这两个方法是空方法,如果我们需要对root进行处理,我们可以用子类继承这个类,然后重写该方法,这种设计模式叫做模板方法。
接着我们进入parseBeanDefinitions方法;该方法是对xml进行读取对bean进行处理。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对beans进行处理
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)) {
//对默认标签处理
this.parseDefaultElement(ele, delegate);
} else {
//对自定义标签处理
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}

}

总结

这次主要探究了spring在出bean处理之前的动作,接下来回顾一下步骤。
1、封装配置文件资源。
2、创建XmlBeanDefinitionReader读取器,接下来用读取器处理资源文件。
3、从配置文件资源中获取文件输入流。
4、获取验证模式,创建可以找到约束文档的EntityResolver
5、进行解析xml文件,获取Document对象,然后解析bean并注册BeanDefitions