xml之自定义解析器-史上最强

命名空间:

概念:

        XML命名空间是一种机制,用于解决在XML文档中可能出现的元素或属性名称冲突问题。当XML文档包含来自不同来源的数据时,可能会出现相同的标签名被多次定义的情况。通过使用命名空间,可以确保即使标签名相同,只要它们属于不同的命名空间,就不会发生冲突。

  • URI(统一资源标识符):每个命名空间都与一个唯一的URI相关联。这个URI并不一定指向实际存在的资源;它更像是一种唯一标识符。(不一定是一个实际的网页,只需要在本文件中具有唯一性个就可以了,不要误以为是解析标签用的,解析标签后面会讲)
  • 前缀:为了方便引用命名空间中的元素和属性,在XML文档中通常会给命名空间分配一个简短的名字作为前缀。
  • 声明:在XML文档中使用xmlns关键字来声明命名空间。如果只声明而不指定前缀,则该命名空间成为默认命名空间。

example:

        不带前缀的是该xml文件的默认命名空间,下面两种是额外自己添加的命名空间。

        

         如果在同一份文件中,使用属于不同的命名空间的相同名称的标签,只需要在前面加上命名空间前缀就可以了~,比如像下面这样,bean标签是属于默认命名空间的标签,无需加前缀,但是下面的扩展点标签是属于sofa的命名空间下面的,就需要添加前缀进行区分命名空间了。

解析过程:   

ApplicationContext的生命周期

        (注意这里不是bean的生命周期,看下面的过程可以了解到bean的生命周期是被包含在以下流程的)

  1. 初始化阶段:

    • 资源定位: 根据配置文件路径(如XML文件、注解等)读取配置信息。
    • 加载Bean定义: 将配置文件中定义的bean转换为内部数据结构,存储在BeanDefinition对象中。
    • 解析Bean依赖关系: 通过分析每个BeanDefinition,确定各个bean之间的依赖关系,并进行相应的处理。
    • 实例化单例bean: 对于标记为单例模式的bean,会在容器启动时立即创建其实例,并完成依赖注入。
  2. 运行阶段:

    • 应用程序在此期间使用由ApplicationContext提供的服务。
    • 可以动态地从上下文中获取或移除bean。
    • 支持事件发布/订阅机制,允许开发者自定义事件监听器并注册到容器中。
  3. 销毁阶段:

    • 当应用程序关闭或者显式调用了close()方法后,ApplicationContext开始销毁过程。
    • 首先会执行所有已注册的销毁回调函数。
    • 然后释放所有被管理的bean以及其占用的资源。
    • 最终清理掉自身的状态,准备垃圾回收。

        下面的XML文件解析和bean初始化会涉及到ApplicationContext的生命周期的标红的两个阶段(再次提醒这里不是bean生命周期)

流程:

        这里我以项目中的一个自定义命名空间为例讲解,会更加的通俗易懂~

        首先抽象的大致步骤分为以下几步~

  1. 实现 BeanDefinitionParser:首先,你需要创建一个实现了 BeanDefinitionParser 接口的类。在这个类中,你需要重写 parse 方法来定义具体的解析逻辑。

  2. 创建 NamespaceHandler:接着,你需要创建一个继承自 NamespaceHandlerSupport 的类,并在这个类中通过 registerBeanDefinitionParser 方法注册上面创建的 BeanDefinitionParser

  3. 注册 NamespaceHandler:最后,为了让 Spring 知道如何找到并使用你的 NamespaceHandler,你需要提供一个名为 META-INF/spring.handlers 的资源文件,在其中指明哪个命名空间应该由哪个 NamespaceHandler 来处理。

  4. 声明命名空间:在你的 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相关生命周期等知识,可以结合一起学习,会有一种豁然开朗的感觉,希望我的文章可以给大家带来帮助,愿君步步高升!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值