聊一聊 Spring 中的擴充套件機制(二) - NamespaceHandler

glmapper發表於2018-08-26

前一篇 聊一聊 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服務】。

小結

本篇通過 NamespaceHandler 瞭解瞭如何去編寫我們自定義的xml標籤,從NamespaceHandler的角度可以很好的理解一些 RPC 框架中最基礎的基於xml 方式的服務引用和暴露的實現思路。另外通過分析 SOFARpc ,也瞭解了在實際的工程元件中對於NamespaceHandler的擴充套件使用。

本文程式碼:glmapper-spring-extention

相關文章