【spring源码系列】之【xml解析】
1. 读源码的方法
java程序员都知道读源码的重要性,尤其是spring的源码,代码设计不仅优雅,而且功能越来越强大,几乎可以与很多开源框架整合,让应用更易于专注业务领域开发。但是能把spring的源码吃透,不仅需要花费大量时间与精力,更需要需要掌握一些方法。下面结合自己读源码与走过的一些弯路,结合网上知名博客专家的建议,整理出以下要点,与读者共勉。
1.1 重视官方英文文档
spring的官方文档写的非常全面,基本可以认为是spring源码思想的一手来源,上面有很多例子不仅帮助读者如何应用,更能帮助我们了解其背后的思想,官方也用大量描述性的文字进行了相关思想的解读,让读者从一个总览上看大致了解spring的核心功能与特性。截止到目前,官方的最新版本是5.3.6,地址如下:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core。
1.2 写示例程序debug源码
通过简单的示例程序,找到源码的入口,通过debug而非仅仅看静态的源码,只有看到真实跑起来的程序以及运行时的值时,心里才大致清楚源码做了哪些事情。
1.3 抓大放小
抓住源码主要流程,而非陷入细节,如果一开始就抠细节,不仅会打消看源码的积极性,也理不清主要流程,最后只能半途而废。只有主流程非常熟悉的情况下,并有时间精力,有兴趣可以深究一些自己感兴趣的细节。
1.4 写源码注释、画流程图
源码的一些重要方法与主要流程可以通过写注释、画流程图来加深理解。
1.5 思考背后的设计思想
源码之所以经典,是因为设计思想优秀,spring的源码在设计模式的灵活应用、类的抽象与封装、框架的可扩展性都做到了极致,可以把该思想以及实践应用到自己的项目设计里面。
1.6 螺旋式学习
任何知识都是循序渐进,源码学习更是如此,因为源码很容易让人半途而废,只有通过刻意重复来逐步提升,每一次都不求甚解,能搞懂多少就搞懂多少,但是每一次都比上一次的理解提升一点,也可参考优质博客系列对比学习,最终将源码的精髓吃透。
2. xml文件解析
2.1 流程概览
上图描述了xml的解析的主要流程,大致分为三个步骤:
step1: 创建BeanFactory
对象;
step2: 解析xml标签,默认标签如bean
、import
,自定义标签<context:component-scan base-package=xxx>
,把标签封装成BeanDefinition
对象;
step3: 最后通过注册机BeanDefinitionRegistry
注册到BeanFactory
的实现类DefaultListableBeanFactory
。
2.2 源码分析
AbstractApplicationContext
类中最重要的方法refresh()
,里面调用很多方法,本文先重点看xml解析相关的方法。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
......
/*
1、创建BeanFactory对象
* 2、xml解析
* 默认标签解析:bean、import等
* 自定义标签解析 如:<context:component-scan base-package="com.xxx"/>
* 自定义标签解析流程:
* a、根据当前解析标签的头信息找到对应的namespaceUri
* b、加载spring所以jar中的spring.handlers文件。并建立映射关系
* c、根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类
* d、调用类的init方法,init方法是注册了各种自定义标签的解析类
* e、根据namespaceUri找到对应的解析类,然后调用paser方法完成标签解析
*
* 3、把解析出来的xml标签封装成BeanDefinition对象
* */
// 告诉子类刷新内部beanFactory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
......
}
进入obtainFreshBeanFactory()
方法,到refreshBeanFactory()
,该方法是个抽象方法,由具体子类实现。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 刷新beanFactory
refreshBeanFactory();
// 获取beanFactory
return getBeanFactory();
}
子类AbstractRefreshableApplicationContext
实现refreshBeanFactory()
方法;
protected final void refreshBeanFactory() throws BeansException {
//如果BeanFactory不为空,则清除BeanFactory和里面的实例
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建beanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//传入beanFactory,并装载BeanDefinition对象
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
上面方法主要做了两件事,一是创建beanFactory
,二是beanFactory
作为参数传入,并负责装载BeanDefinition对象。接下来进入loadBeanDefinitions(beanFactory)
方法,该方法是个抽象方法,交给子类AbstractXmlApplicationContext
去实现,子类方法如下。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//为给定的BeanFactory创建xml的解析器,这里是一个委托模式
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
//这里传一个this进去,因为ApplicationContext是实现了ResourceLoader接口的
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
//传入解析器,装载BeanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}
上述方法创建xml解析器XmlBeanDefinitionReader
,并交由解析器完成BeanDefinitions
的装载。再次进入AbstractXmlApplicationContext
类的loadBeanDefinitions
方法。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//获取需要加载的xml配置文件
String[] configLocations = getConfigLocations();
if (configLocations != null) {
// xml解析器完成装载
reader.loadBeanDefinitions(configLocations);
}
}
再次进入xml解析器的loadBeanDefinitions
方法,
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
......
//把字符串类型的xml文件路径,转换成Resource对象
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 传入Resource对象装载BeanDefinitions
int count = loadBeanDefinitions(resources);
......
进入上述的loadBeanDefinitions(resources)
方法;
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//把inputSource 封装成Document文件对象
Document doc = doLoadDocument(inputSource, resource);
//根据document对象,去注册BeanDefinitions
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
上述方法一是封装Document
文件对象,二是用Document
对象去注册BeanDefinitions
,随后进入registerBeanDefinitions
方法;
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//创建BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//并委托BeanDefinitionDocumentReader这个类进行document的解析
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
该方法创建BeanDefinitionDocumentReader
对象,并委托其解析document,进入registerBeanDefinitions
方法。
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);
}
}
上述方法主要完成默认标签解析,与自定义标签解析,默认标签如同import
、alias
、bean
、beans
,自定义标签比如<aop:aspectj-autoproxy />
,默认标签重点分析bean
标签的解析;
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//bean签解析
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//完成document到BeanDefinition对象转换后,对BeanDefinition对象进行缓存注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
......
}
}
上述方法主要是bean
标签的解析,最后对BeanDefinition
对象进行缓存注册,先分析解析;
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
......
//创建BeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//解析BeanDefinition里面的属性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析meta元素
parseMetaElements(ele, bd);
//解析Lookup方法
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析ReplacedMethod方法
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析构造参数方法
parseConstructorArgElements(ele, bd);
//解析属性元素方法
parsePropertyElements(ele, bd);
//解析Qualifier元素方法
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
// 最后返回beanDefinition
return bd;
再分析注册BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())
,进入该方法,最后会进入DefaultListableBeanFactory
类,
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
//先判断BeanDefinition是否已经注册
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
//把beanDefinition缓存到map中
this.beanDefinitionMap.put(beanName, beanDefinition);
//把beanName放到beanDefinitionNames list中
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
最终看出所谓的注册到BeanFactory
的容器中的类,无非就是一个定义了ConcurrentHashMap
类型的beanDefinitionMap
。
自定义标签解析BeanDefinitionParserDelegate
类,执行parseCustomElement
方法;
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取namespaceURI
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 解析namespaceURI对应的handler类
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 执行handler的解析方法
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
上述过程主要完成以下步骤:
step1
:获取namespaceURI;
step2
:解析namespaceURI对应的handler类;
step3
:执行handler方法解析。
其中step2又分为几个步骤,代码进入如下方法
public NamespaceHandler resolve(String namespaceUri) {
// 从spring.handler里面获取映射关系
Map<String, Object> handlerMappings = getHandlerMappings();
// 根据namespaceURI从映射关系map中获取对应的处理类handler
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
// 通过反射实例化namespaceHandler类
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 实例化namespaceHandler对象
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 执行init方法
namespaceHandler.init();
// 把新的namespaceUri与namespaceHandler映射关系组装到map中
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
上述解析namespaceURI对应的handler类,对应步骤又课分为如下几步,
step1
:从spring.handler里面获取映射关系;
step2
:根据namespaceURI从映射关系map中获取对应的处理类handler;
step3
:通过反射获取handler对象,并执行init方法,完成自定义标签注册;
3. 总结
本文主要分析了xml标签的解析,主要步骤与流程图上述代码分析与时序图,通过调试可以清晰观察到解析过程,后续将通过示例分析beanDefinition
类的相关属性。
【spring源码系列】之【xml解析】的更多相关文章
- spring源码-增强容器xml解析-3.1
一.ApplicationContext的xml解析工作是通过ClassPathXmlApplicationContext来实现的,其实看过ClassPathXmlApplicationContext ...
- Spring源码追踪2——xml解析入口
解析xml节点入口 org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDe ...
- Spring源码-IOC部分-Xml Bean解析注册过程【3】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Ioc容器BeanPostProcessor-Spring 源码系列(3)
Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...
- Ioc容器beanDefinition-Spring 源码系列(1)
Ioc容器beanDefinition-Spring 源码系列(1) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器 ...
- Spring源码系列 — 注解原理
前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...
- Spring源码系列 — BeanDefinition扩展点
前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition ...
- Spring源码系列 — Bean生命周期
前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...
- Spring源码系列 — BeanDefinition
一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...
- AOP执行增强-Spring 源码系列(5)
AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...
随机推荐
- django学习-7.html模板中include标签使用场景
1.前言 假设一个公司A有一个网站B,且网站B有5个不同的页面分别为C1,C2,C3,C4,C5. 那么,我们在打开这5个不同页面后去查看页面的整体内容,会发现每个页面的顶部内容.底部内容都一模一样. ...
- ubuntu ARM换国内源和国内源安装ROS
ubuntu arm换国内源: https://www.cnblogs.com/yongy1030/p/10315569.html 国内源安装ROS: https://blog.csdn.net/ch ...
- Linux文件和零拷贝
本文转载自文件和零拷贝 文件概述 文件描述符 文件描述符:在Linux中,所有的文件都是通过文件描述符引用.fd是一个非负整数.按照惯例,标准输入的fd是0,标准输出的fd是1,标准错误的fd是2.分 ...
- 顶级c程序员之路 基础篇 - 第一章 关键字的深度理解 number-1
c语言有32个关键字,每个关键字你都理解吗? 今天出场的是: auto , register, static, extern 为什么他们会一起呢,说到这里不得不谈到c语言对变量的描述. c给每 ...
- WPF -- 自定义按钮
本文介绍WPF一种自定义按钮的方法. 实现效果 使用图片做按钮背景: 自定义鼠标进入时效果: 自定义按压效果: 自定义禁用效果 实现效果如下图所示: 实现步骤 创建CustomButton.cs,继承 ...
- if __name__ == '__main__':简单粗暴解释
这个脚本被执行的时候,__name__ 值就是 __main__ ,才会执行 main()函数如果这个脚本是被 import 的话,__name__的值不一样.main()函数就不会被调用.这个句子用 ...
- 洛谷 P4747 [CERC2017]Intrinsic Interval 线段树维护连续区间
题目描述 题目传送门 分析 考虑对于 \([l,r]\),如何求出包住它的长度最短的好区间 做法就是用一个指针从 \(r\) 向右扫,每次查询以当前指针为右端点的最短的能包住 \([l,r]\) 的好 ...
- 剑指 Offer 61. 扑克牌中的顺子 + 简单题 + 思维
剑指 Offer 61. 扑克牌中的顺子 Offer_61 题目描述 java代码 package com.walegarrett.offer; /** * @Author WaleGarrett * ...
- 剑指 Offer 10- II. 青蛙跳台阶问题
剑指 Offer 10- II. 青蛙跳台阶问题 Offer 10- II 题目描述: 动态规划方程: 循环求余: 复杂度分析: package com.walegarrett.offer; impo ...
- Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker
创建.运行及共享容器镜像 P23 运行容器 P24 运行 P24 可以运行 Docker 客户端可执行文件来执行各种 Docker 命令.例如:可以试着从 Docker Hub 的公共镜像仓库拉取.运 ...