Spring解密之XML解析与Bean注册示例详解

为什么开始看spring的源码

半路转行写代码快一年半了,从开始工作就在使用spring框架,虽然会用,会搭框架,但是很多时候不懂背后的原理,比如:spring是怎样控制事务的,springmvc是怎样处理请求的,aop是如何实现的...这让人感觉非常不踏实,那就开始慢慢边看书边研究spring的源码吧!!!

怎样高效的看源码

我的答案是带着具体的问题去看源码,不然非常容易陷入源码细节中不能自拔,然后就晕了,最后你发现这看了半天看的是啥玩意啊.

引言

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 (本地下载)

您可能感兴趣的文章:

  • 详解Spring中使用xml配置bean的细节
  • Spring装配Bean教程之XML安装配置bean详解
(0)

相关推荐

  • Spring装配Bean教程之XML安装配置bean详解

    前言 众所周知在Spring刚出现的时候,XML是描述配置的主要方式,在Spring的名义下,我们创建了无数行XML代码.在一定程度上,Spring成为了XML的同义词. 现在随着强大的自动化配置和Java代码的配置出现,XML不再是唯一选择,也不应该是首选,学习XML配置,更多用于维护已有的XML的配置.下面话不多说了,来一起看看详细的介绍吧. 创建XML配置规范 在使用XML配置前,需要创建一个新的配置规范,就像JavaConfig需要我们创建带有 @Configuration注解的类,而在

  • 详解Spring中使用xml配置bean的细节

    整理总结刚学的关于spring的xml配置bean的知识. 在ApplicationContext.xml文件中使用bean节点配置bean,bean的属性id在IOC容器中必须是唯一的. <bean id="helloWorld" class="com.test.spring.beans.HelloWorld"> <property name="name" value="Spring"></pr

  • Spring解密之XML解析与Bean注册示例详解

    为什么开始看spring的源码 半路转行写代码快一年半了,从开始工作就在使用spring框架,虽然会用,会搭框架,但是很多时候不懂背后的原理,比如:spring是怎样控制事务的,springmvc是怎样处理请求的,aop是如何实现的...这让人感觉非常不踏实,那就开始慢慢边看书边研究spring的源码吧!!! 怎样高效的看源码 我的答案是带着具体的问题去看源码,不然非常容易陷入源码细节中不能自拔,然后就晕了,最后你发现这看了半天看的是啥玩意啊. 引言 Spring是一个开源的设计层面框架,解决了

  • Spring容器的创建过程之如何注册BeanPostProcessor详解

    注册BeanPostProcessor refresh()调用registerBeanPostProcessors(beanFactory)方法,注册Bean的后置处理器,后置处理器是用来拦截bean创建过程的. 不同接口类型的BeanPostProcessor,即继承了BeanPostProcessor接口的子接口,在Bean创建前后的执行时机是不一样的 BeanPostProcessor DestructionAwareBeanPostProcessor InstantiationAware

  • Spring依赖注入(DI)两种方式的示例详解

    目录 一.依赖注入方式 二.setter注入 引用类型 简单类型 三.构造器注入 引用类型 简单类型 参数适配(了解) 四.依赖注入方式选择 一.依赖注入方式 思考:向一个类中传递数据的方式有几种? 普通方法(set方法) 构造方法 思考:依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢? 引用类型 简单类型(基本数据类型与String) 依赖注入方式: setter注入 简单类型 引用类型 构造器注入 简单类型 引用类型 二.setter注

  • java 与testng利用XML做数据源的数据驱动示例详解

    java 与testng利用XML做数据源的数据驱动示例详解 testng的功能很强大,利用@DataProvider可以做数据驱动,数据源文件可以是EXCEL,XML,YAML,甚至可以是TXT文本.在这以XML为例: 备注:@DataProvider的返回值类型只能是Object[][]与Iterator<Object>[] TestData.xml: <?xml version="1.0" encoding="UTF-8"?> <

  • Python实现解析yaml配置文件的示例详解

    目录 楔子 字典 数组 标量 引用 生成 yaml 文件 楔子 前面我们介绍了 ini 格式的配置文件,本次来看看 yaml,它的表达能力相比 ini 更加的强大.yaml 文件以 .yml 结尾,在介绍它的语法结构之前我们先来看看 yaml 的一些基本规则. 大小写敏感: 使用缩进表示层级关系,并且缩进只能用空格.不可以使用 tab 键.缩进的空格数目不重要,只要相同层级的元素左侧对齐即可: # 表示注释,# 到行尾的所有字符都会被忽略: yaml 支持的数据结构有以下三种: 字典:键值对的集

  • Python实现解析ini配置文件的示例详解

    目录 楔子 ini 文件 特殊格式 小结 楔子 在开发过程中,配置文件是少不了的,只不过我们有时会将 py 文件作为配置文件(config.py),然后在其它的模块中直接导入.这样做是一个好主意,不过配置文件是有专门的格式的,比如:ini, yaml, toml 等等. 而对于 Python 而言,也都有相应的库来解析相应格式的文件,下面我们来看看 ini 文件要如何解析. ini 文件 先来了解一下 ini 文件的格式: [satori] name = 古明地觉 age = 16 where 

  • 深入分析XmlSerializer对象的Xml序列化与反序列化的示例详解

    这篇随笔对应的.Net命名空间是System.Xml.Serialization:文中的示例代码需要引用这个命名空间.为什么要做序列化和反序列化?.Net程序执行时,对象都驻留在内存中:内存中的对象如果需要传递给其他系统使用:或者在关机时需要保存下来以便下次再次启动程序使用就需要序列化和反序列化.范围:本文只介绍xml序列化,其实序列化可以是二进制的序列化,也可以是其他格式的序列化.看一段最简单的Xml序列化代码 复制代码 代码如下: class Program{    static void

  • Spring Boot如何优化内嵌的Tomcat示例详解

    前言 本文主要给大家介绍了关于Spring Boot优化内嵌Tomcat的相关内容,分享出来供大家参考学习,下面话不多说了,来一看看详细的介绍吧. Spring Boot测试版本 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE&l

  • Spring 与 JDK 线程池的简单使用示例详解

    1.配置自定义共享线程池(Spring线程池) @Configuration @EnableAsync public class ThreadPoolConfig{ //主要任务的调度,计划执行 @Bean("taskScheduler") public Executor createScheduler(){ // 创建一个线程池对象 ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); // 定义一个线程

  • Spring Boot集成netty实现客户端服务端交互示例详解

    前言 Netty 是一个高性能的 NIO 网络框架,本文主要给大家介绍了关于SpringBoot集成netty实现客户端服务端交互的相关内容,下面来一起看看详细的介绍吧 看了好几天的netty实战,慢慢摸索,虽然还没有摸着很多门道,但今天还是把之前想加入到项目里的 一些想法实现了,算是有点信心了吧(讲真netty对初学者还真的不是很友好......) 首先,当然是在SpringBoot项目里添加netty的依赖了,注意不要用netty5的依赖,因为已经废弃了 <!--netty--> <

随机推荐