转载

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

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

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

NamespaceHandler

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

聊一聊 Spring 中的扩展机制 - NamespaceHandler
因为这里我只引入了 beancontext 依赖,所以这也仅仅是一部分。图中我们常用的应该算是 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
正文到此结束
Loading...