《系列一》-- 5、xml配置文件解析之[自定义]命名空间[标签]的解析

系列,xml,配置文件,解析,自定义,命名,空间,标签 · 浏览次数 : 34

小编点评

自定义标签学习流程如下: 1.自定义标签的声明:xsd 文件中定义我们自定义的标签的属性。 2.编写Spring.handlers 和 Spring.schemas在spring.beans 模块下找到:Spring.handlers 和 Spring.schemas我们这两个文件中追加我们自己的配置:5 NamespaceHandler 解读第四章所描述的就是我们的: custom-bean标签的配置过程了。 3.第三章结尾的问题,也能答上来了,这里我们需要注意的NamespaceHandlerResolver.resolve() 干了啥?【Ctrl + Alt + 鼠标左键】找到NamespaceHandlerResolver接口resolve方法唯一实现getHandlerMappings 的代码就不放了,刚兴趣可以自己看。里边的内容是:从第四章中提到的:Spring.handlers 文件中,根据标签命名空间,获取到我们自定义的:CustomModelHandler-- 4.NamespaceHandler().parse() 干了啥?经过上一环节,这里我们拿到了 NamespaceHandler 继续深入 parse() 方法:因为我们继承的 NamespaceHandlerSupport 抽象类,所以这里直接看它的parse方法这里一共两件事:5.2.1 提取自定义标签解析器parser = findParserForElement(element, parserContext); 方法提取通过 CustomModelHandler 注入容器的,自定义标签解析器:CustomModelParser5.2.2 执行解析动作(parser != null ? parser.parse(element, parserContext) : null)当解析器不为空,执行解析的 parse 方法,自定义的 CustomModelParser 类覆写的超类方法是:doParse(Element element, BeanDefinitionBuilder builder)根据 CustomModelParser 的类图,我们从而知道,parser.parse() 的实现逻辑在:AbstractBeanDefinitionParser.parse()到此为止,调用到我们自己重写的 doParse() 方法了,最终,上述的:parseInternal() 解析得到 BeanDefinition 后,会将其注册到 容器中。这里的 parserContext 的来路也可以追溯。至于 readerContext 的来路,我想不用我再说了吧,前文已经追溯过了,它打哪来:自定义标签的学习也到此为止了。 归纳总结以上内容,生成内容时需要带简单的排版。

正文




阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。



Spring源码阅读系列--全局目录.md



引子

1、容器最基本使用.md

系列1 - bean 标签解析:

2、XmlBeanFactory 的类图介绍.md

3、XmlBeanFactory 对xml文件读取.md

4、xml配置文件解析之【默认】命名空间【标签】的解析.md

5、xml配置文件解析之【自定义】命名空间【标签】的解析.md

系列2 - bean 获取: getBean() 做了什么

前言

一句话概括:

  • 如何基于spring 的xml配置文件整活:怎么创建我们自己的标签?
  • 自定义标签读取,并注册到:BeanFactory 中 (BeanDefinitionRegistry)

【只读取配置内容,并注册管理】

1 为什么要用自定义命名空间标签 ?

因为spring-beans 支持的只是将默认配置读取,并解析到 Bean 这种比较通用的逻辑。

如果我们希望对 xml 注入的bean的 成员变量 进行:

  • 二次加工
  • 参数值校验

等等个性化操作时,spring 官方实现的标签就不太适合做这种操作了,所以我们需要根据自己的业务去自定义标签。

2 前文回顾

书接上回,上文讲了 spring 默认命名空间标签解析:

本文将从 自定义标签的解析开始展开。

稍微回顾下,这里的:【delegate】 的来历,它是:DefaultBeanDefinitionDocumentReader 类的成员变量,在不做提前配置的时候,它的默认值是:null

再看下图对 delegate 的初始化操作,所以前文的两个猜想,已经有了答案了,delegate 只有下图中唯一的一种类型。
对于自定义标签的解析,也只是在其上增加了一些配置,至于具体是什么样的配置,让 delegate 可以支持对,用户自定义标签的解析呢?

如下章节将围绕这个主题展开:

3 命名空间提取

我们基于,已知逻辑继续阅读代码:

delegate.parseCustomElement(ele); // 自定义命名空间(书签)

图中标注的3行代码表示如下3个行为:

3.1 从xml 元素获取命名空间uri

没啥好说的,就是简单xml文件内容中,关于自定标签的命名空间信息读取。

3.2 自定义标签 - 解析器获取

目光放到这里:

NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

记住这个类名:[NamespaceHandler] 世界线会收束的,我们还会见到它的。

readerContext 是谁? 它从哪里来?

回顾下前文代码就知道了:

  • 看看 红框那行代码的名字,世界又闭环了。

【tag:问题1】

NamespaceHandlerResolver.resolve() 干了啥?

【tag:问题1】

3.3 自定义标签内容的解析

再追踪 NamespaceHandler.parse(xx, xxx) 方法,我们发现,走不下去了:

【tag:问题2】

按下 【Ctrl + Alt + 鼠标左键】 发现不止一个类实现了NamespaceHandler接口,我们并不能确定下一步走向何方了。

【tag:问题2】

3.4 todo

为了解答 【3.2节】 和 【3.3节】 提出问题,在第四章中,我们将简单演示下,自定义标签的使用,看完第四章,这两个问题将得到初步解答。

4 自定义命名空间标签使用案例

同默认命名空间标签解析一样,这里只讨论从自定义标签,解析成 BeanDefinition 的过程

4.1 Bean定义

package org.springframework.custom.bean;
public class CustomModelBean {

	private String modelName;
	private String desc;
	private String action;
	public String getAction() {
		return action;
	}
	public void setAction(String action) { this.action = action; }
	public String getModelName() { return modelName; }
	public void setModelName(String modelName) { this.modelName = modelName; }
	public String getDesc() { return desc; }
	public void setDesc(String desc) { this.desc = desc; }
}

4.2 自定义标签解析器 - 逻辑实现

package org.springframework.custom.parser;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.custom.bean.CustomModelBean;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
 * 继承抽象类 AbstractSingleBeanDefinitionParser 实现自定义标签的解析逻辑
 * - 它会负责将xml 配置的标签解析成 BeanDefinition的形式,最终注册到 BeanFactory [ > BeanDefinitionRegistry]
 */
public class CustomModelParser extends AbstractSingleBeanDefinitionParser {
	@Override
	protected Class<?> getBeanClass(Element element) {
		return CustomModelBean.class;
	}
	@Override
	protected void doParse(Element element, BeanDefinitionBuilder builder) {
		String modelName = element.getAttribute("modelName");
		String desc = element.getAttribute("desc");
		String action = element.getAttribute("action");
		// 将从自定义标签提取到的属性注册到:BeanDefinitionBuilder;最终会注册到 BeanFactory中
		if (StringUtils.hasText(modelName)) {
			builder.addPropertyValue("modelName", modelName);
		}
		if (StringUtils.hasText(desc)) {
			builder.addPropertyValue("desc", desc);
		}
		if (StringUtils.hasText(action)) {
			builder.addPropertyValue("action", action);
		}
	}
}

4.3 将:自定义标签解析器 注册到 Spring 容器

这里,我们自定的标签名是:

  • custom-model

我们可以在xml 文件里使用它去配置 自定义的Bean

package org.springframework.custom.handler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.custom.parser.CustomModelParser;
public class CustomModelHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		/**
		 * 注册自定义标签的解析器到 BeanFactory 中
		 */
		registerBeanDefinitionParser("custom-model", new CustomModelParser());
	}
}

4.4 自定义标签声明:xsd 文件编辑

前文有提到过,spring 会通过xsd 或者 dtd 文件,对配置的xml进行校验。

如下的 xsd 文件,声明了,我们的自定义标签的属性;我们定义的:custom-bean 标签在被解析前,需要通过如下 xsd文件的校验。
【至于为啥要做校验,为了防止xml注入攻击应该是原因之一】

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- 这里的案例只是伪代码,这里贴的命名空间也是伪代码 -->
<xsd:schema xmlns="http://www.bokerr.com/schema/custom-bean"
			xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			targetNamespace="http://www.bokerr.com/schema/custom-bean"
			elementFormDefault="qualified">
	<xsd:element name="custom-bean">
		<xsd:complexType>
			<xsd:attribute name="id" type="xsd:string"/>
			<xsd:attribute name="modelName" type="xsd:string"/>
			<xsd:attribute name="desc" type="xsd:string"/>
			<xsd:attribute name="action" type="xsd:string"/>
		</xsd:complexType>
	</xsd:element>
</xsd:schema>

4.5 编辑 Spring.handlers 和 Spring.schemas

在 spring.beans 模块下找到:Spring.handlers 和 Spring.schemas
我们这两个文件中追加我们自己的配置:

5 NamespaceHandler 解读

第四章所描述的就是我们的: custom-bean 标签的配置过程了。

第三章结尾的问题,也能答上来了,这里我们需要关注的 NamespaceHandler 的实现类是:

  • NamespaceHandlerSupport 它是个抽象类

而我们在 CustomModelHandler 类中重写的就是 NamespaceHandlerSupport 的 init() 方法,我们在这里注入了,自定义标签的解析器:

  • CustomModelParser

5.1 未完的问题-【问题1】: NamespaceHandlerResolver.resolve() 干了啥?

【Ctrl + Alt + 鼠标左键】 找到NamespaceHandlerResolver接口resolve方法唯一实现

getHandlerMappings 的代码就不放了,刚兴趣可以自己看.
里边的内容是:

  • 从第四章中提到的:Spring.handlers 文件中,根据标签命名空间,
    获取到我们自定义的:

  • CustomModelHandler

    • --> NamespaceHandlerSupport
      • --> NamespaceHandler

5.2 未完的问题-【问题2】: NamespaceHandler().parse() 干了啥?

经过上一环节,这里我们拿到了 NamespaceHandler 继续深入 parse() 方法:
因为我们继承的 NamespaceHandlerSupport 抽象类,所以这里直接看 它的parse 方法

这里一共两件事:

5.2.1 提取自定义标签解析器

parser = findParserForElement(element, parserContext); 方法

提取通过 CustomModelHandler 注入容器的,自定义标签解析器:

  • CustomModelParser

5.2.2 执行解析动作

(parser != null ? parser.parse(element, parserContext) : null)

当解析器不为空,执行解析的 parse 方法,自定义的 CustomModelParser 类覆写的超类方法是:

  • doParse(Element element, BeanDefinitionBuilder builder)

根据 CustomModelParser 的类图,我们从而知道, parser.parse() 的实现逻辑在:

  • AbstractBeanDefinitionParser.parse()

到此为止,调用到我们自己重写的 doParse() 方法了,
最终,上述的:parseInternal() 解析得到 BeanDefinition 后,会将其注册到 容器中。

这里的 parserContext 的来路也可以追溯。

至于 readerContext 的来路,我想不用我再说了吧,前文已经追溯过了,它打哪来:


自定义标签的学习也到此为止了。

与《系列一》-- 5、xml配置文件解析之[自定义]命名空间[标签]的解析相似的内容:

《系列一》-- 5、xml配置文件解析之[自定义]命名空间[标签]的解析

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 Spring源码阅读系列--全局目录.md 引子 1、容器最基本使用.md 系列1 - bean 标签解析: 2、XmlBeanFactory

《系列一》-- 4、xml配置文件解析之[默认]命名空间[标签]的解析

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 Spring源码阅读系列--全局目录.md 引子 1、容器最基本使用.md 系列1 - bean 标签解析: 2、XmlBeanFactory

《系列一》-- 3、XmlBeanFactory 对xml文件读取

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 Spring源码阅读系列--全局目录.md 引子 1、容器最基本使用.md 系列1 - bean 标签解析: 2、XmlBeanFactory

《系列一》-- 1、容器最基本使用

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 Spring源码阅读系列--全局目录.md 引子 1、容器最基本使用.md 系列1 - bean 标签解析: 2、XmlBeanFactory

《系列一》-- 2、XmlBeanFactory 的类图介绍.md

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 Spring源码阅读系列--全局目录.md 引子 1、容器最基本使用.md 系列1 - bean 标签解析: 2、XmlBeanFactory

Kafka源码分析(四) - Server端-请求处理框架

系列文章目录 https://zhuanlan.zhihu.com/p/367683572 一. 总体结构 先给一张概览图: 服务端请求处理过程涉及到两个模块:kafka.network和kafka.server。 1.1 kafka.network 该包是kafka底层模块,提供了服务端NIO通信

回归模型的算法性能评价

一、概述 在一般形式的回归问题中,会得到系列的预测值,它们与真实值(ground truth)的比较表征了模型的预测能力,为有效量化这种能力,常见的性能评价指标有可解释方差(EVS)、平均绝对误差(MAE)、均方误差(MSE)、均方根误差(RMSE)、决定系数(R2)等。值得一提的是,回归问题分单输

[转帖]Django系列3-Django常用命令

文章目录 一. Django常用命令概述二. Django常用命令实例2.1 help命令2.2 version2.3 check2.4 startproject2.5 startapp2.6 runserver2.7 shell2.8 migrations2.8.1 makemigrations2

机器学习算法(一):1. numpy从零实现线性回归

系列文章目录 机器学习算法(一):1. numpy从零实现线性回归 机器学习算法(一):2. 线性回归之多项式回归(特征选取) @目录系列文章目录前言一、理论介绍二、代码实现1、导入库2、准备数据集3、定义预测函数(predict)4 代价(损失)函数5 计算参数梯度6 批量梯度下降7 训练8 可视

[转帖]一起来体验96核心、192线程CPU——第四代AMD EPYC处理器独家测试

http://k.sina.com.cn/article_1882475282_70344b12027010s1x.html 与第三代EPYC 7003系列处理器相比,新一代EPYC 9004系列处理器有大量的技术进步,主要包括核心数量、计算线程数大幅提升到最高96核心、192线程;5nm“Zen