命名空间:
概念:
XML命名空间是一种机制,用于解决在XML文档中可能出现的元素或属性名称冲突问题。当XML文档包含来自不同来源的数据时,可能会出现相同的标签名被多次定义的情况。通过使用命名空间,可以确保即使标签名相同,只要它们属于不同的命名空间,就不会发生冲突。
- URI(统一资源标识符):每个命名空间都与一个唯一的URI相关联。这个URI并不一定指向实际存在的资源;它更像是一种唯一标识符。(不一定是一个实际的网页,只需要在本文件中具有唯一性个就可以了,不要误以为是解析标签用的,解析标签后面会讲)
- 前缀:为了方便引用命名空间中的元素和属性,在XML文档中通常会给命名空间分配一个简短的名字作为前缀。
- 声明:在XML文档中使用
xmlns
关键字来声明命名空间。如果只声明而不指定前缀,则该命名空间成为默认命名空间。
example:
不带前缀的是该xml文件的默认命名空间,下面两种是额外自己添加的命名空间。
如果在同一份文件中,使用属于不同的命名空间的相同名称的标签,只需要在前面加上命名空间前缀就可以了~,比如像下面这样,bean标签是属于默认命名空间的标签,无需加前缀,但是下面的扩展点标签是属于sofa的命名空间下面的,就需要添加前缀进行区分命名空间了。
解析过程:
ApplicationContext的生命周期
(注意这里不是bean的生命周期,看下面的过程可以了解到bean的生命周期是被包含在以下流程的)
-
初始化阶段:
- 资源定位: 根据配置文件路径(如XML文件、注解等)读取配置信息。
- 加载Bean定义: 将配置文件中定义的bean转换为内部数据结构,存储在
BeanDefinition
对象中。 - 解析Bean依赖关系: 通过分析每个
BeanDefinition
,确定各个bean之间的依赖关系,并进行相应的处理。 - 实例化单例bean: 对于标记为单例模式的bean,会在容器启动时立即创建其实例,并完成依赖注入。
-
运行阶段:
- 应用程序在此期间使用由
ApplicationContext
提供的服务。 - 可以动态地从上下文中获取或移除bean。
- 支持事件发布/订阅机制,允许开发者自定义事件监听器并注册到容器中。
- 应用程序在此期间使用由
-
销毁阶段:
- 当应用程序关闭或者显式调用了
close()
方法后,ApplicationContext
开始销毁过程。 - 首先会执行所有已注册的销毁回调函数。
- 然后释放所有被管理的bean以及其占用的资源。
- 最终清理掉自身的状态,准备垃圾回收。
- 当应用程序关闭或者显式调用了
下面的XML文件解析和bean初始化会涉及到ApplicationContext的生命周期的标红的两个阶段(再次提醒这里不是bean生命周期)
流程:
这里我以项目中的一个自定义命名空间为例讲解,会更加的通俗易懂~
首先抽象的大致步骤分为以下几步~
实现 BeanDefinitionParser:首先,你需要创建一个实现了
BeanDefinitionParser
接口的类。在这个类中,你需要重写parse
方法来定义具体的解析逻辑。创建 NamespaceHandler:接着,你需要创建一个继承自
NamespaceHandlerSupport
的类,并在这个类中通过registerBeanDefinitionParser
方法注册上面创建的BeanDefinitionParser
。注册 NamespaceHandler:最后,为了让 Spring 知道如何找到并使用你的
NamespaceHandler
,你需要提供一个名为META-INF/spring.handlers
的资源文件,在其中指明哪个命名空间应该由哪个NamespaceHandler
来处理。声明命名空间:在你的 Spring 配置文件(通常是 XML 文件)顶部声明相应的命名空间 URI,并在需要的地方使用自定义标签。
因为是NamespaceHandler注册的BeanDefinitionParser,所以我这里以代码实际的执行顺序讲解。
1. 注册BeanDefinitionParser
NamespaceHandler
是Spring框架中用于处理XML配置文件中的自定义命名空间的一个接口。当Spring容器启动并遇到使用了特定命名空间(比如你自定义的某个命名空间)的XML配置时,它会查找对应的NamespaceHandler
实现类,并调用其init()
方法,init方法内部一般是调用注册BeanDefinitionParser的方法。但在实际开发中我们一般会实现NamespaceHandlerSupport,而不直接实现NamespaceHandler。
这是NamespaceHandler的类结构,共包含下面三种方法:
下面是我的项目中的某一个NamespaceHandler的实现类,可以看到init方法里面只调用了注册parser的方法。
拓展:
看上图中,注册方法内部没有手动的指出具体的BeanDefinitionParser实现类,而是使用
ServiceLoader.load(Sofa3MiddlewareTagNameSupport.class);
来找出Sofa3MiddlewareTagNameSupport接口的实现类集合,逐一进行注册。这里使用的是一种解耦的方案,可以更好的实现热插拔,在以后的升级系统的过程中,可以不更改原有代码,只需更改配置文件即可实现灵活解析。
SPI(Service Provider Interface):
通俗的来讲就是接口和实现分离的一种技术,可以实现解耦,研发人员可以更加灵活的通过配置的方式为某个接口指定实现类。在服务提供方,研发人员无需关心接口的具体实现,只需要指定相关抽象接口即可。而具体的实现由第三方编写并指定。
使用流程:
1. 定义服务接口:服务提供方编写
2. 实现服务接口:第三方服务商编写
3. 创建服务配置文件:第三方服务商编写
4. 使用 ServiceLoader 加载服务
示例:
1. 首先我们查看该字节码文件对应的类文件。重点是要看其所在目录,定义接口也在这里展示了。
2. 实现服务接口:第三方服务商编写
后续会更新相关文章,这次先关注大体架构。
3. 创建服务配置文件:第三方服务商编写
命名规则是服务提供方的接口的全类名。上图中已标出。下图是配置文件内容,也就代表着服务提供方定义的接口对应的实现类为以下四种。通俗的来讲,即使第三方服务商提供了很多个实现,因为配置文件只指定了四种,那在加载类时也只会加载这四个实现。
4. 使用 ServiceLoader 加载服务
不再赘述。
真正的注册流程,可以发现首先获取了相关beandefinitionParser的tagname
注册流程为将tagname和对应的解析器放入本地缓存中
发现tagname就是xml文件中该命名空间下的某一个标签,那就代表,当spring解析xml文件时遇到该标签就会匹配对应的BeandefinationParser进行解析。
2. 编写BeanDefinationParser实现类
首先查看BeanDefinationParser类结构,只有一个parse函数,spring会帮我们自动调用。
具体的解析过程由于过于复杂,这里不在赘述。
3. 注册NamespaceHandler
拓展:
如果遇到xml没有指定schema的情况,可以查看根据NamespaceHandlerSupport找到对应二方包进行查阅。总而言之,思想是灵活的,不要局限于某一处死磕,与君共勉。
![]()
4. 声明命名空间:
总结:
其实这篇文章本来想讲解sofa扩展点相关功能,研究过程有些跑题,索性改成了xml解析,这也算其中很重要的一环,本文涉及springcontext相关生命周期等知识,可以结合一起学习,会有一种豁然开朗的感觉,希望我的文章可以给大家带来帮助,愿君步步高升!