Spring(九)- Spring自定义命名空间整合第三方框架原理解析

news2024/12/27 11:34:05

文章目录

  • 一、Spring通过命名空间整合第三方框架
    • 1. Dubbo 命名空间
    • 2. Context 命名空间
  • 二、Spring自定义命名空间原理解析
  • 三、手写自定义命名空间标签与Spring整合

一、Spring通过命名空间整合第三方框架

1. Dubbo 命名空间

Spring 整合其他组件时就不像MyBatis这么简单了,例如Dubbo框架在与Spring进行整合时,要使用Dubbo提供的命名空间的扩展方式,自定义了一些Dubbo的标签,Dubbo框架再去解析自己的Dubbo标签

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	<!--引入dubbo命名空间-->
	xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd 
	<!--引入dubbo对应的schema映射地址-->
	http://dubbo.apache.org/schema/dubbo 
	http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
	<!--配置应用名称--> 
	<dubbo:application name="dubbo1-consumer"/>
	<!--配置注册中心地址--> 
	<dubbo:registry address="zookeeper://localhost:2181"/>
	<!--扫描dubbo的注解--> 
	<dubbo:annotation package="com.itheima.controller"/>
	<!--消费者配置--> 
	<dubbo:consumer check="false" timeout="1000" retries="0"/>
</beans>

2. Context 命名空间

为了降低我们此处的学习成本,不再引入Dubbo第三方框架了,以Spring的 context 命名空间去进行讲解,该方式也是命名空间扩展方式。

需求:加载外部properties文件jdbc.properties,将键值对存储在Spring容器中

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

引入context命名空间,再使用context命名空间的标签,使用SpEL表达式在xml或注解中根据key获得value

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	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
	http://www.springframework.org/schema/context 
	http://www.springframework.org/schema/context/spring-context.xsd"> 
	
	<!--加载properties文件-->
	<context:property-placeholder location="classpath:jdbc.properties" />
	
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> 
		<property name="driverClassName" value="${jdbc.driver}"></property> 
		<property name="url" value="${jdbc.url}"></property> 
		<property name="username" value="${jdbc.username}"></property> 
		<property name="password" value="${jdbc.password}"></property>
	</bean> 
<beans>

其实,加载的properties文件中的属性最终通过Spring解析后会被存储到了Spring容器的environment中去,不仅自己定义的属性会进行存储,Spring也会把环境相关的一些属性进行存储
在这里插入图片描述

二、Spring自定义命名空间原理解析

首先从ClassPathXmlApplicationContext入手,进入spring容器入口refresh()方法

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			...
			// 将beanDefinition定义信息填充到beanDefinitionMap中
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			...
		}
	}
}

接着调用AbstractApplicationContext类中的obtainFreshBeanFactory方法

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
			refreshBeanFactory();
			return getBeanFactory();
	}
}

接着执行AbstractRefreshableApplicationContext类中的refreshBeanFactory方法

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
	@Override
	protected final void refreshBeanFactory() throws BeansException {
		...
		loadBeanDefinitions(beanFactory);
		...
	}
}

最终会通过loadBeanDefinitions方法->doLoadBeanDefinitions方法->registerBeanDefinitions方法->doRegisterBeanDefinitions方法->parseBeanDefinitions方法

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
	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)) {
						// 默认命名空间的标签有import、alias、bean、beans
						parseDefaultElement(ele, delegate);
					} else {
						// 解析自定义命名空间的标签
						delegate.parseCustomElement(ele);
					}
				}
			}
		} else {
			delegate.parseCustomElement(root);
		}
	}
}

在这里插入图片描述

默认命名空间的标签有import、alias、bean、beans

在这里插入图片描述

由于context是自定义命名空间的标签,所以会执行delegate.parseCustomElement(ele);方法

public class BeanDefinitionParserDelegate {
	@Nullable
	public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
		// xmlns:context="http://www.springframework.org/schema/context"
		// 这里的namespaceUri对应的是xml中的"http://www.springframework.org/schema/context"
		String namespaceUri = getNamespaceURI(ele);
		// 根据命名空间uri获取命名空间处理器
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		// 执行命名空间处理器的parse方法,解析执行的标签
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}
}

根据命名空间uri获取命名空间处理器

public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {

	// 通过构造方法创建DefaultNamespaceHandlerResolver时,会将"META-INF/spring.handlers"中配置的命名空间处理器加载到handlerMappings中
	public DefaultNamespaceHandlerResolver() {
		this(null, "META-INF/spring.handlers");
	}
	
	// 在执行resovle方法时,就是从Map<String, Object> handlerMappings中根据命名空间名称获得对应的处理器对象,此处是ContextNamespaceHandler,最终执行NamespaceHandler的parse方法
	@Override
	@Nullable
	public NamespaceHandler resolve(String namespaceUri) {
		// key是命名空间uri,value是命名空间处理器
		Map<String, Object> handlerMappings = getHandlerMappings();
		...
		String className = handlerMappings.get(namespaceUri);
		Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
		// 此时这里的命名空间处理器就是ContextNamespaceHandler
		NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
		// 执行ContextNamespaceHandler的init方法
		namespaceHandler.init();
		handlerMappings.put(namespaceUri, namespaceHandler);
		return namespaceHandler;
		...
	}
}

在这里插入图片描述
在这里插入图片描述

执行ContextNamespaceHandler的init方法
ContextNamespaceHandler源码如下,间接实现了NamespaceHandler接口,初始化方法init会被自动调用。由于context命名空间下有多个标签,所以每个标签又单独注册了对应的解析器,注册到了其父类NamespaceHandlerSupport的Map<String, BeanDefinitionParser> parsers中去了

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		// 注入解析器,不同的标签有不同的解析器
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}
}

注入解析器后,再去执行命名空间处理器ContextNamespaceHandler的parse方法,该parse方法具体流程是再去调每个自定义标签解析器的parse方法,例如上面"property-placeholder"标签,会执行PropertyPlaceholderBeanDefinitionParser解析器的parse方法,parse方法主要作用就是解析标签的beanDefinition定义信息注册到容器中,Spring再根据对应的beanDefinition定义信息创建对象

public abstract class NamespaceHandlerSupport implements NamespaceHandler {
	@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		return (parser != null ? parser.parse(element, parserContext) : null);
	}
}

在这里插入图片描述

通过上述分析,我们清楚的了解了外部命名空间标签的执行流程,如下:
⚫ 将自定义标签的约束:物理约束文件与网络约束名称的约束 以键值对形式存储到一个spring.schemas文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;
⚫ 将自定义命名空间的名称 与 自定义命名空间的处理器映射关系 以键值对形式存在到一个叫spring.handlers文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;
⚫ 准备好NamespaceHandler,如果命名空间只有一个标签,那么直接在parse方法中进行解析即可,一般解析结果就是注册该标签对应的BeanDefinition。如果命名空间里有多个标签,那么可以在init方法中为每个标签都注册一个BeanDefinitionParser,在执行NamespaceHandler的parse方法时再分流给不同的BeanDefinitionParser进行解析(重写doParse方法即可)。

在这里插入图片描述
在这里插入图片描述

http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context.xsd

约束文件http://www.springframework.org/schema/context/spring-context.xsd对应真实的路径是org/springframework/context/config/spring-context.xsd
在这里插入图片描述

三、手写自定义命名空间标签与Spring整合

设想自己是一名架构师,进行某一个框架与Spring的集成开发,效果是通过一个指示标签,向Spring容器中自动注入一个BeanPostProcessor,这样可以在创建bean的生命周期中对自定义框架中的bean进行增强处理

步骤分析:

  1. 确定命名空间名称、schema虚拟路径、标签名称;
  2. 编写schema约束文件haohao-annotation.xsd
  3. 在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
  4. 编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser
  5. 编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注册HaohaoBeanPostProcessor
  6. 编写HaohaoBeanPostProcessor
    ========== 以上五步是框架开发者写的,以下是框架使用者写的===========
  7. 在applicationContext.xml配置文件中引入命名空间
  8. 在applicationContext.xml配置文件中使用自定义的标签
<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:xsi="http://www.w3.org/2001/xmlSchema-instance"
		xmlns:haohao="http://www.itheima.com/haohao"
		xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.itheima.com/haohao
		http://www.itheima.com/haohao/haohao-annotation.xsd">
		<haohao:annotation-driven/>
</beans>

编写schema约束文件haohao-annotation.xsd

<?xml version="1.0" encoding="UTF-8"?>
	<xsd:schema xmlns="http://www.itheima.com/haohao"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	targetNamespace="http://www.itheima.com/haohao">
	<xsd:element name="annotation-driven"></xsd:element>
</xsd:schema>

在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
在这里插入图片描述
编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser

public class HaohaoNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		this.registerBeanDefinitionParser("annotation-driven", new HaohaoBeanDefinitionParser());
	}
}

编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注册HaohaoBeanPostProcessor

public class HaohaoBeanDefinitionParser implements BeanDefinitionParser {
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		// 创建HaohaoBeanPostProcessor的BeanDefinition
		RootBeanDefinition beanDefinition = new RootBeanDefinition();
		beanDefinition.setBeanClass(HaohaoBeanPostProcessor.class);
		// 注册HaohaoBeanPostProcessor
		parserContext.getRegistry().registerBeanDefinition("haohaoBeanPostProcessor", beanDefinition);
			return beanDefinition;
	}
}

编写HaohaoBeanPostProcessor

public class HaohaoBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("创建bean时,可以在此进行增强处理...");
		return bean;
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/11787.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电影影院购票管理系统

1、项目介绍 电影影院购票管理系统拥有两种角色&#xff1a;管理员和用户 管理员&#xff1a;用户管理、影片管理、影厅管理、订单管理、影评管理、排片管理等 用户&#xff1a;登录注册、个人中心、查看电影票、电影选座、下单支付、发布影评、查看票房统计等 2、项目技术 …

14、Horizontal Pod Autoscal

一、为何进行缩扩容&#xff1f; 在实际生产中&#xff0c;经常会遇到某个服务需要扩容的场景&#xff0c;可能会遇到由于资源紧张或者工作负载降低而需要减少服务实例数量的场景。可以利用Deployment/RC的Scale机制来完成这些工作。二、缩扩容模式 Kubernetes 对 Pod 扩容与缩…

mysql-Innodb解析

一.计算机不同介质操作速度 相对于CPU和内存操作&#xff0c; 我们可以看到磁盘的操作延时明显要大得多&#xff0c; 一次磁盘搜索的延时需要10ms。 假入我们某一个业务操作进行了大量磁盘读写&#xff0c; 那可以预料到这个服务的性能肯定是非常差的&#xff0c; 那么到底是什…

3.2文法与语言

1、文法生成语言 推导 定义&#xff1a;当αAβ直接推导出αγβ&#xff0c;即αAβ⇒αγβ&#xff0c;仅当A→γ是一个产生式&#xff0c;且α,β∈(VT∪VN)*。 注&#xff1a;按照我的理解是两个字符串的推导。如果α1⇒α2⇒…⇒αn,则我们称这个序列是从α1到αn的一个…

动态规划01 背包问题(算法)

上篇文章说了&#xff0c;查找组成一个偶数最接近的两个素数算法&#xff1a; 查找组成一个偶数最接近的两个素数https://blog.csdn.net/ke1ying/article/details/127872594 本篇文章题目是 动态规划01 背包问题&#xff1a; 背包容量5kg&#xff0c;现在有三个物体&#xf…

BVH动捕文件导入到E3D骨骼树

BVH动捕文件导入到E3D骨骼树 1. BVH动捕文件 BVH动作捕捉文件有两部分组成&#xff0c;第一部分描述了静止状态下角色的基本骨骼结构&#xff0c;角色通常处于Apose或Tpose姿态下&#xff0e;文本用树状结构描述了各个关节点的相对位置(OFFSET xyz)&#xff0c;连接两关节点的…

学好MySQL增删查改,争取不做CURD程序员【下篇(六个小时肝MySQL万字大总结)】

✨✨hello&#xff0c;愿意点进来的小伙伴们&#xff0c;你们好呐&#xff01; &#x1f43b;&#x1f43b;系列专栏&#xff1a;【MySQL初阶】 &#x1f432;&#x1f432;本篇内容&#xff1a;一套打通MySQL基础操作. &#x1f42f;&#x1f42f;作者简介:一名现大二的三非编…

解决小程序-wx.canvasGetImageData()-RGB取色盘苹果手机获取颜色慢问题

简介 最近做了一个微信小程序控制蓝牙设备&#xff0c;通过小程序中的RGB取色盘&#xff0c;获取当前的RGB颜色&#xff0c;通过蓝牙发送给设备&#xff0c;设备接收到RGB以后&#xff0c;做出相应的调整。 图1&#xff1a;RGB取色盘 在安卓手机上运行正常&#xff0c;能够迅速…

企业实战项目rsync+inotify实现实时同步

目录 一、inotify安装和介绍 1. 安装inotify 2. inotify-tools常用命令 3. rsync inotify 实践 3.1 服务端配置 3.2 客户端配置 一、inotify安装和介绍 1. 安装inotify yum install epel-release -y yum install inotify-tools -y 2. inotify-tools常用命令 inotify-to…

C++ 使用哈希表封装模拟实现unordered_map unordered_set

一、unordered_map unordered_set 和 map set的区别 1. map set底层采取的红黑树的结构&#xff0c;unordered_xxx 底层数据结构是哈希表。unordered_map容器通过key访问单个元素要比map快&#xff0c;但它通常在遍历元素子集的范围迭代方面效率较低。 2. Java中对应的容器名…

vivo和oppo通知权限弹窗

在vivo和oppo部分手机上&#xff0c;首次安装app时&#xff0c;会弹出一个系统级的通知权限弹窗&#xff0c;&#xff08;部分一加手机也会出现&#xff0c;是因为一加手机使用了OPPO的colorOS系统&#xff09;如图。 这个通知权限弹窗比较坑&#xff0c;一来可能不符合产品对…

Word控件Spire.Doc 【文本】教程(21) ;如何在 C# 中用 Word 文档替换文本

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

ARM pwn 入门 (4)

3. callme 本题有多个文件&#xff0c;根据题意来看是需要依次调用callme_one、callme_two、callme_three这3个库函数。这三个函数会进行flag的解密&#xff0c;但是需要满足前三个参数等于特定值。这就需要我们去elf文件中寻找合适的gadget了。 而本题正好给出了一个不能再合…

为什么模板的声明与定义不能分离?

目录 一、模板的好处与注意事项 二、 声明定义为什么不能不放一起&#xff1f; 一、模板的好处与注意事项 模板的好处从下面代码可以体现&#xff1a; template<typename T> void Swap(T& left, T& right) {T temp left;left right;right temp; } int main…

IDEA+MapReduce+Hive综合实践——搜狗日志查询分析

1.下载数据源 打开搜狗实验室链接&#xff1a;搜狗搜索引擎 - 上网从搜狗开始&#xff0c;由于搜狗实验室链接打不开了&#xff0c;所有这里自己制作一份数据进行实验。 SogouQ.txt: 2.上传下载文件至HDFS 2.1将下载的文件通过FinalShell工具上传到Linux系统 2.2SogouQ.txt并…

dev_I_II笔记

dev1 问题 1.model创建不了&#xff1f; 2.从开发界面双击进入后&#xff0c;如何返回&#xff1f; 3.一个客户端界面的所有东西就是一个项目吗&#xff1f;多个项目的数据全部储存在开发界面的aot中&#xff1f; 4.最终的测试是需要完成一个什么东西&#xff1f; 1.学习…

企业微信自定义客户画像

OpenAPI基本介绍 微伴助手开放接口对接文档 version: 0.4 注1&#xff1a;微伴助手开放接口已经开放公测&#xff0c;进入微伴后台-应用管理-API接入以获取企业标识&#xff08;corp_id&#xff09; &#xff0c;企业API调用凭据&#xff08;secret&#xff09; 注2&#xff…

视图相关知识的汇总

重点大纲 描述视图创建&#xff0c;改变视图的定义&#xff0c;删除视图通过视图重新找回数据通过视图插入&#xff0c;更新和删除数据创建和使用inline视图执行Top-N 分析什么是视图&#xff1f; 视图是基于一张表或者另一张视图的逻辑表。 视图本身不包含数据。视图被存储在…

[数据结构]什么是树?什么是二叉树?

作者&#xff1a; 华丞臧. 专栏&#xff1a;【数据结构】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 文章目录一、树1.1 树的概念及结构1.2 树的相关概念1.3 树的表示1.4 树在实际中的运用二…

JavaEE——Http请求和响应,https的加密

请求 报头 里面是一系列键值对&#xff0c;有的是标准定义的&#xff0c;有的是自定义的 典型的有以下几个 Host 代表服务器的主机地址和端口 也就是当我们访问浏览器时&#xff0c;可以知道从哪里获取数据 端口号如果省略就代表是默认值&#xff0c;http是80&#xff0c;h…