聊一聊 Spring 中的扩展机制 – NamespaceHandler

前一篇 聊一聊 Spring 中的扩展机制(一) 中聊到了 ApplicationListenerApplicationContextAwareBeanFactoryAware 三种机制。本篇将介绍 NamespaceHandler 的扩展使用。

相信很多小伙伴对于这几个类都不陌生,基本基于 java 实现的 RPC 框架都会使用,比如 Dubbo , SOFARpc 等。本文先从几个小 demo 入手,了解下基本的概念和编程流程,然后分析下 SOFARpc 中是如何使用的。

NamespaceHandler

NamespaceHandlerSpring 提供的 命名空间处理器。下面这张图中,除了乱入的本篇 demo 中涉及到的 BridgeNameSpaceHandler 之外,其他均为 Spring 自身提供的。

聊一聊 Spring 中的扩展机制 - NamespaceHandler

因为这里我只引入了
bean
context 依赖,所以这也仅仅是一部分。图中我们常用的应该算是
AopNamespaceHandler

我们使用基于 xmlspring 配置时,可能需要配置如 <aop:config /> 这样的标签,在配置这个标签之前,通常我们需要引入这个 aop 所在的命名空间:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/aop


http://www.springframework.org/schema/aop/spring-aop.xsd


http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd" />
复制代码

关于AOP 可以了解下 聊一聊 AOP :表现形式与基础概念 ,这里不过多解释,下面就按照官方文档的流程 来写一个自定义 xml ,最终效果如下:

<bridge:application id="bridgeTestApplication"
                    name="bridgeTestApplication"  
                    version="1.0" 
                    organization="bridge.glmapper.com"
                    owner="leishu@glmapper"/>
复制代码

1、定义 xsd 文件

关于 xsd 文件的语法规则不在本篇范围之内,有兴趣的同学可以自行 google 。 下面这个文件很简单,定义的 element name 为 application ,对应于 bridge:application 中的 applicationattribute 就是上面效果展示中对应的几个属性名。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:beans="http://www.springframework.org/schema/beans"
            xmlns:tool="http://www.springframework.org/schema/tool"
            xmlns="http://bridge.glmapper.com/schema/bridge"
            targetNamespace="http://bridge.glmapper.com/schema/bridge">

    <xsd:import namespace="http://www.springframework.org/schema/beans"/>

    <xsd:complexType name="applicationType">
        <xsd:attribute name="id" type="xsd:ID"/>
        <xsd:attribute name="name" type="xsd:string" use="required"/>
        <xsd:attribute name="version" type="xsd:string"/>
        <xsd:attribute name="owner" type="xsd:string"/>
        <xsd:attribute name="organization" type="xsd:string"/>
    </xsd:complexType>

    <xsd:element name="application" type="applicationType"/>
</xsd:schema>
复制代码

2、编写 NamespaceHandler

In addition to the schema, we need a NamespaceHandler that will parse all elements of this specific namespace Spring encounters while parsing configuration files.

用编写的这个 NamespaceHandler解析配置文件。

具体说来 NamespaceHandler 会根据 schema 和节点名找到某个 BeanDefinitionParser ,然后由 BeanDefinitionParser 完成具体的解析工作。

Spring 提供了默认实现类 NamespaceHandlerSupportAbstractSingleBeanDefinitionParser ,最简单的方式就是去继承这两个类。

这里通过继承 NamespaceHandlerSupport 这个抽象类来完成。

public class BridgeNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("application", 
        new ApplicationBeanDefinitionParser());
    }
}
复制代码

这里实际上只是注册了一个解析器,具体的 BeanDefinitionParser 才是将 XML 元素映射到特定 bean 的。

3、编写 BeanDefinitionParser

这里直接通过实现 BeanDefinitionParser 接口的方式定义我们的 BeanDefinitionParser 实现类。关于 AbstractSingleBeanDefinitionParser 的使用在 SPFARpc 中会涉及到。

public class ApplicationBeanDefinitionParser implements BeanDefinitionParser {

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //beanDefinition
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(ApplicationConfig.class);
        beanDefinition.setLazyInit(false);
        //解析id
        String id = element.getAttribute("id");
        beanDefinition.getPropertyValues().add("id", id);
        //解析name
        beanDefinition.getPropertyValues().add("name",
        element.getAttribute("name"));
        //解析version
        beanDefinition.getPropertyValues().add("version",
        element.getAttribute("version"));
        //owner
        beanDefinition.getPropertyValues().add("owner",
        element.getAttribute("owner"));
        //organization
        beanDefinition.getPropertyValues().add("organization",
        element.getAttribute("organization"));
    
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
        return beanDefinition;
    }
}
复制代码

这里我们需要了解的是开始解析自定义标签的时候,是通过 BeanDefinitionParserDelegate->parseCustomElement 方法来处理的,如下图所示:

聊一聊 Spring 中的扩展机制 - NamespaceHandler

通过 ele 元素拿到当前 namespaceUri ,也就是在 xsd 中定义的命名空间,接着委托给 DefaultNamespaceResolver 得到具体的 handlerBridgenamspaceHandler ) , 然后执行 parse 解析。

4、配置 spring.handlers 和 spring.schmas

http/://bridge.glmapper.com/schema/bridge=
com.glmapper.extention.namespacehandler.BridgeNamespaceHandler

http/://bridge.glmapper.com/schema/bridge.xsd=META-INF/bridge.xsd
复制代码

配置这个其实是为了让 Spring 在解析 xml 的时候能够感知到我们的自定义元素,我们需要把 NamespaceHandlerxsd 文件放到位于META-INF目录下的 spring.handlersspring.schmas 文件中。这样就可以在 spring 配置文件中使用我们自定义的标签了。如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:bridge="http://bridge.glmapper.com/schema/bridge"
       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://bridge.glmapper.com/schema/bridge


http://bridge.glmapper.com/schema/bridge.xsd">

    <bridge:application id="bridgeTestApplication"
                    name="bridgeTestApplication"  
                    version="1.0" 
                    organization="bridge.glmapper.com"
                    owner="leishu@glmapper"/>
</beans>
复制代码

验证下从容器中获取我们的 bean

public static void main(String[] args) {
    ApplicationContext applicationContext = new
    ClassPathXmlApplicationContext("classpath:bean.xml");
    
    ApplicationConfig applicationConfig = (ApplicationConfig)
    applicationContext.getBean("bridgeTestApplication");
    
    System.out.println("applicationConfig = "+applicationConfig);
}
复制代码

输出示例:

applicationConfig = ApplicationConfig {
    id=bridgeTestApplication, 
    name='bridgeTestApplication', 
    version='1.0', 
    owner='leishu@glmapper', 
    organization='bridge.glmapper.com'
}
复制代码

整体来看,如果我们要实现自己的 xml 标签,仅需完成以下几步即可:

  • 1、定义 xsd 文件
  • 2、编写 NamespaceHandler
  • 3、编写 BeanDefinitionParser
  • 4、配置 spring.handlers 和 spring.schmas

SOFARpc 中使用分析

SOFARpc 中的 rpc.xsd 文件是集成在 sofaboot.xsd 文件中的,详细可见: sofa-boot

聊一聊 Spring 中的扩展机制 - NamespaceHandler

xsd 文件这里不贴了,有点长

spring.handlers 和 spring.schmas

先看下 spring.handlersspring.schmas 配置:

http/://sofastack.io/schema/sofaboot=
com.alipay.sofa.infra.config.spring.namespace.handler.SofaBootNamespaceHandler

http/://sofastack.io/schema/sofaboot.xsd=
META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/sofaboot.xsd

http/://sofastack.io/schema/rpc.xsd=
META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/rpc.xsd

复制代码

spring.handlers 找到 NamespaceHandler : SofaBootNamespaceHandler

SofaBootNamespaceHandler

源码如下,这里看出来,并不是像上面我们自己写的那种方式那样,会有一个 BeanDefinitionParser 。这里其实设计的很巧妙,通过 spi 的方式来载入具体的 BeanDefinitionParser

public class SofaBootNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        ServiceLoader<SofaBootTagNameSupport> serviceLoaderSofaBoot =
        ServiceLoader.load(SofaBootTagNameSupport.class);
        //SOFABoot
        for (SofaBootTagNameSupport tagNameSupport : serviceLoaderSofaBoot) {
            this.registerTagParser(tagNameSupport);
        }
    }

    private void registerTagParser(SofaBootTagNameSupport tagNameSupport) {
        if (!(tagNameSupport instanceof BeanDefinitionParser)) {
            // log
            return;
        }
        String tagName = tagNameSupport.supportTagName();
        registerBeanDefinitionParser(tagName, (BeanDefinitionParser)
        tagNameSupport);
    }
}
复制代码

这里可以看出有 ReferenceDefinitionParserServiceDefinitionParser 两个解析类,分别对应服务引用和服务暴露。

聊一聊 Spring 中的扩展机制 - NamespaceHandler

下面以 ReferenceDefinitionParser 为例,先看下它的类图

聊一聊 Spring 中的扩展机制 - NamespaceHandler

解析工作都是在 AbstractContractDefinitionParser 类中完成, ReferenceDefinitionParser 自己只是做了一些特殊处理【jvm-first,jvm服务】。

原文 

https://juejin.im/post/5b812878f265da437a4699c7

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 聊一聊 Spring 中的扩展机制 – NamespaceHandler

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址