您的位置:首页 > 编程语言 > Java开发

Spring解密 - XML解析 与 Bean注册

2018-01-09 17:47 661 查看
Spring
是一个开源的设计层面框架,解决了业务逻辑层和其他各层的松耦合问题,将面向接口的编程思想贯穿整个系统应用,同时它也是
Java工作中
必备技能之一…

前言

由于记录的是
Spring源码分析
的过程,详细用法就不一一赘述了

核心代码

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>


用法

public class Application {

public static void main(String[] args) {

BeanDefinitionRegistry beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
ClassPathResource resource = new ClassPathResource("bean.xml");
//整个资源加载的切入点。
reader.loadBeanDefinitions(resource);
}
}


解密

DefaultListableBeanFactory
Spring
注册及加载
bean
的默认实现,整个
Spring Ioc模板
中它可以称得上
始祖


跟踪
DefaultListableBeanFactory
,可以发现如下代码块,该设计的目的是什么?

public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}


举例来说,当
A
中有属性
B
时,那么
Spring
在获取属性
A
时,如果发现属性
B
未实例化则会自动实例化属性
B
,这也是
Spring
中提供的一个重要特性,在某些情况下
B
不会被初始化,比如实现了
BeanNameAware
接口。

Spring
中是这样介绍的:自动装配时忽略给定的依赖接口,比如通过其他方式解析
Application
上下文注册依赖,类似于
BeanFactory
通过
BeanFactoryAware
进行的注入或者
ApplicationContext
通过
ApplicationContextAware
进行的注入。

资源管理

通过
Resource
接口来实现对
File、URL、Classpath
等资源的管理,
Resource
负责对配置文件进行读取,即将配置文件封装为
Resource
,然后交给
XmlBeanDefinitionReader
来处理。



XML 解析

XmlBeanDefinitionReader
Spring
资源文件读取、解析、注册的实现,要重点关注该类。

跟踪
reader.loadBeanDefinitions(resource);
,我们可以见到如下
核心代码(剔除注释和抛出异常)


public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
}


上文代码首先对
Resource
做了一次编码操作,目的就是担心 XML 存在编码问题

仔细观察
InputSource inputSource = new InputSource(inputStream);
,它的包名居然是
org.xml.sax
,所以我们可以得出
Spring
采用的是
SAX解析
,使用
InputSource
来决定如何读取 XML 文件。

最后将准备的数据通过参数传入到真正核心处理部分
doLoadBeanDefinitions(inputSource, encodedResource.getResource())


获取 Document

1.
doLoadBeanDefinitions(inputSource, encodedResource.getResource());
省略若干catch和注释


protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
}


2.
doLoadDocument(inputSource, resource);


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


首先通过
getValidationModeForResource
获取 XML 文件的验证模式(DTD 或者 XSD),可以自己设置验证方式,默认是开启
VALIDATION_AUTO
即自动获取验证模式的,通过
InputStream
读取 XML 文件,检查是否包含
DOCTYPE
单词,包含的话就是 DTD,否则返回 XSD。

常见的 XML 文件验证模式有:

public class XmlValidationModeDetector {
/**
* Indicates that DTD validation should be used (we found a "DOCTYPE" declaration).
*/
public static final int VALIDATION_DTD = 2;

/**
* Indicates that XSD validation should be used (found no "DOCTYPE" declaration).
*/
public static final int VALIDATION_XSD = 3;

public int detectValidationMode(InputStream inputStream) throws IOException {

}
}


this.documentLoader.loadDocument
方法中涉及到一个
EntityResolver
参数

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
}


何为
EntityResolver
? 官方解释: 如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口,并使用
setEntityResolver
方法向SAX 驱动器注册一个实例。也就是说,对于解析一个 xml,sax 首先会读取该 xml 文档上的声明,根据声明去寻找相应的 DTD 定义,以便对文档的进行验证,默认的寻找规则,(即:网络下载,通过 XML 声明的 DTD URI地址来下载 DTD的定义),并进行认证,下载的过程是一个漫长的过程,而且当网络不可用时,这里会报错,就是因为相应的 dtd 没找到。

EntityResolver
的作用是项目本身就可以提供一个如何寻找 DTD 声明的方法,即由程序来实现寻找 DTD 的过程,这样就避免了通过网络来寻找相应的声明。



3.
EntityResolver
接受两个参数:

public abstract InputSource resolveEntity (String publicId,String systemId)
throws SAXException, IOException;


3.1 定义
bean.xml
文件,内容如下(XSD模式)

<?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"> 
</beans>


解析到如下两个参数:

publicId: null

systemId: http://www.springframework.org/schema/beans/spring-beans.xsd

3.2 定义
bean.xml
文件,内容如下(DTD模式)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

</beans>


解析到如下两个参数:

publicId: -//SPRING//DTD BEAN 2.0//EN

systemId: http://www.springframework.org/dtd/spring-beans.dtd

3.3 Spring 使用
DelegatingEntityResolver
来解析
EntityResolver


public class DelegatingEntityResolver {

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}

}


我们可以看到针对不同的模式,采用了不同的解析器

DTD: 采用
BeansDtdResolver
解析,直接截取
systemId
最后的
*.dtd(如:spring-beans.dtd)
,然后去当前路径下寻找

XSD: 采用
PluggableSchemaResolver
解析,默认加载
META-INF/Spring.schemas
文件下与
systemId
所对应的 XSD 文件

注册 Bean

看完解析
XML校验
后,继续跟踪代码,看
Spring
是如何根据
Document
注册
Bean
信息

public class XmlBeanDefinitionReader {

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 创建DocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 记录统计前的 BeanDefinition 数
int countBefore = getRegistry().getBeanDefinitionCount();
// 注册 BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 记录本次加载 BeanDefinition 的个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
}


注册
Bean
的时候首先使用一个
BeanDefinitionParserDelegate
类来判断是否是默认命名空间,实现是通过判断
namespace uri
是否和默认的
uri
相等:

public class BeanDefinitionParserDelegate {

public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

public boolean isDefaultNamespace(@Nullable String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

}


跟踪
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
,其中
doc
是通过前面代码块中
loadDocument
转换出来的,这个方法主要目的就是提取出
root 节点(beans)


public class DefaultBeanDefinitionDocumentReader {

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}

}


跟踪
doRegisterBeanDefinitions(root)
,我们将看到如下处理流程

protected void doRegisterBeanDefinitions(Element root) {

// ...
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
// ...

// 空实现
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
// 空实现
postProcessXml(root);

this.delegate = parent;
}


首先对
profile
解析(比较常见的玩法就是不同
profile
初始化的
bean
对象不同,实现多环境)


接下来的解析使用了
模板方法模式
,其中
preProcessXml
postProcessXml
都是空方法,为的就是方便之后的子类在解析前后进行一些处理。只需要覆写这两个方法即可。

解析并注册
BeanDefinition
,该部分代码比较简单

public class DefaultBeanDefinitionDocumentReader {

/**
* 解析 root 节点下的其它节点 import", "alias", "bean".
* @param root节点名称
*/
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)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}

/**
* 处理 Bean 标签,然后将其注册到注册表中去
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
}


委托
BeanDefinitionParserDelegate
类的
parseBeanDefinitionElement
方法进行元素解析,返回
BeanDefinitionHolder
类型的实例
bdHolder
(包含了配置文件的各个属性
class、name、id、alias
等)

当返回的
bdHolder
不为空的情况下,若默认标签的子节点存在自定义属性,则再次对自定义标签进行解析

解析完毕后,委托
BeanDefinitionReaderUtils.registerBeanDefinition();
bdHolder
进行注册

发送注册事件,告知相关监听
Bean
已经注册成功了

总结

熬过几个无人知晓的秋冬春夏,撑过去一切都会顺着你想要的方向走…

说点什么

全文代码:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter1

个人QQ:1837307557

battcn开源群(适合新手):391619659

微信公众号:
battcn
(欢迎调戏)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐