Spring 可擴展XML配置機制實踐

用過dubbo的同學應該很熟悉下面的配置:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <!-- 提供方應用信息,用於計算依賴關係 -->
    <dubbo:application name="hello-world-app"  />

    <!-- 使用zk爲註冊中心暴露服務地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 用dubbo協議在20880端口暴露服務 -->
    <dubbo:protocol name="dubbo" port="20880" />

    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />

    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />

</beans>

我們通過 dubbo:service 對外提供RPC服務,一切都司空見慣。但你有沒有想過dubbo:service 到底是什麼?Spring是怎麼解析它並把它注入到容器中的?

本文着重介紹Spring Framework 基於Schema風格的XML擴展機制,通過Spring提供的xml擴展機制,我們可以在spring.xml中加入自己的標籤,之後Spring會幫我們解析並納入自己的管理範圍內。

環境配置

JDK 1.7
Spring 4.3.3.RELEASE
Maven 3.3
IDEA 15

示例

通過學習 Spring Extensible XML ,自己寫了一個類似dubbo 自定義標籤的demo樣例供大家學習,工程結構如下圖所示:
這裏寫圖片描述

最後,我們也可以在Spring配置文件引入我們自定義的標籤了,如下:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:rpc="http://www.bytebeats.com/schema/rpc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.bytebeats.com/schema/rpc http://www.bytebeats.com/schema/rpc/rpc.xsd">

    <rpc:registry id="zk" protocol="zookeeper" address="127.0.0.1" />
    <rpc:protocol id="hessian" name="hessian" port="9001"/>

    <rpc:service id="rpcService" ref="helloService" interface="com.bytebeats.spring4.extension.service.IHelloService" timeout="5000" retries="1"></rpc:service>

    <rpc:ref id="accountService" interface="com.bytebeats.spring4.extension.service.IAccountService" retries="0" check="false" />

    <bean id="helloService" class="com.bytebeats.spring4.extension.service.impl.HelloServiceImpl" />

</beans>

1、定義XML schema

首先,我們需要定義一個xsd文件來聲明XML標籤元素,本文中爲rpc.xsd,如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.bytebeats.com/schema/rpc"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:beans="http://www.springframework.org/schema/beans"
            targetNamespace="http://www.bytebeats.com/schema/rpc"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified">

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

    <xsd:complexType name="abstractConfig">
        <xsd:choice minOccurs="0" maxOccurs="unbounded">
            <xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
        </xsd:choice>
        <xsd:anyAttribute namespace="##other" processContents="lax" />
    </xsd:complexType>

    <xsd:element name="service">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="abstractConfig">
                    <xsd:choice minOccurs="0" maxOccurs="unbounded">
                        <xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
                    </xsd:choice>
                    <xsd:attribute name="id" type="xsd:ID" />
                    <xsd:attribute name="ref" type="xsd:string" use="required"/>
                    <xsd:attribute name="interface" type="xsd:string" use="required"/>
                    <xsd:attribute name="group" type="xsd:string" use="optional"/>
                    <xsd:attribute name="registry" type="xsd:string" use="optional"/>
                    <xsd:attribute name="version" type="xsd:string" use="optional"/>
                    <xsd:attribute name="timeout" type="xsd:string" use="optional"/>
                    <xsd:attribute name="retries" type="xsd:string" use="optional"/>
                    <xsd:attribute name="async" type="xsd:boolean" use="optional"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>

    <xsd:element name="ref">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="abstractConfig">
                    <xsd:choice minOccurs="0" maxOccurs="unbounded">
                        <xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
                    </xsd:choice>
                    <xsd:attribute name="id" type="xsd:ID" />
                    <xsd:attribute name="interface" type="xsd:string" use="required"/>
                    <xsd:attribute name="group" type="xsd:string" use="optional"/>
                    <xsd:attribute name="registry" type="xsd:string" use="optional"/>
                    <xsd:attribute name="version" type="xsd:string" use="optional"/>
                    <xsd:attribute name="timeout" type="xsd:string" use="optional"/>
                    <xsd:attribute name="retries" type="xsd:string" use="optional"/>
                    <xsd:attribute name="async" type="xsd:boolean" use="optional"/>
                    <xsd:attribute name="check" type="xsd:boolean" use="optional"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>

    <xsd:element name="registry">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="abstractConfig">
                    <xsd:choice minOccurs="0" maxOccurs="unbounded">
                        <xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
                    </xsd:choice>
                    <xsd:attribute name="id" type="xsd:ID" />
                    <xsd:attribute name="protocol" type="xsd:string" use="required"/>
                    <xsd:attribute name="address" type="xsd:string" use="required"/>
                    <xsd:attribute name="username" type="xsd:string" use="optional"/>
                    <xsd:attribute name="password" type="xsd:string" use="optional"/>
                    <xsd:attribute name="check" type="xsd:boolean" use="optional"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>

    <xsd:element name="protocol">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="abstractConfig">
                    <xsd:choice minOccurs="0" maxOccurs="unbounded">
                        <xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded" />
                    </xsd:choice>
                    <xsd:attribute name="id" type="xsd:ID" />
                    <xsd:attribute name="name" type="xsd:string" use="required"/>
                    <xsd:attribute name="port" type="xsd:string" use="required"/>
                    <xsd:attribute name="host" type="xsd:string" use="optional"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

關於XML Schema這裏不詳述了,大家可以參考 w3school XML Schema 簡介

2、定義解析器

定義一個BeanDefinitionParser負責解析xml,如下:

package com.bytebeats.spring4.extension.xml;

import com.bytebeats.spring4.extension.domain.RpcServiceBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @create 2016-11-23 11:50
 */
public class RpcServiceBeanDefinitionParser extends AbstractBeanDefinitionParser {

    @Override
    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {

        return parseComponet(element, parserContext);
    }

    private AbstractBeanDefinition parseComponet(Element element, ParserContext parserContext) {

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RpcServiceBean.class);

        String id = element.getAttribute("id");
        if (StringUtils.hasText(id)) {
            builder.addPropertyValue("id", id);
        }

        String ref = element.getAttribute("ref");
        builder.addPropertyValue("ref", ref);

        String interfaceName = element.getAttribute("interface");
        builder.addPropertyValue("interfaceName", interfaceName);

        String group = element.getAttribute("group");
        if (StringUtils.hasText(group)) {
            builder.addPropertyValue("group", group);
        }

        String registry = element.getAttribute("registry");
        if (StringUtils.hasText(registry)) {
            builder.addPropertyValue("registry", registry);
        }

        String version = element.getAttribute("version");
        if (StringUtils.hasText(version)) {
            builder.addPropertyValue("version", version);
        }

        String timeout = element.getAttribute("timeout");
        if (StringUtils.hasText(timeout)) {
            builder.addPropertyValue("timeout", Integer.parseInt(timeout));
        }

        String retries = element.getAttribute("retries");
        if (StringUtils.hasText(retries)) {
            builder.addPropertyValue("retries", Integer.parseInt(retries));
        }

        String async = element.getAttribute("async");
        if (StringUtils.hasText(async)) {
            builder.addPropertyValue("async", Boolean.valueOf(async));
        }

        return builder.getBeanDefinition();
    }

}

3、定義NamespaceHandler

package com.bytebeats.spring4.extension.xml;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @create 2016-11-23 11:48
 */
public class RpcNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("service", new RpcServiceBeanDefinitionParser());
        registerBeanDefinitionParser("ref", new RpcReferenceBeanDefinitionParser());
        registerBeanDefinitionParser("registry", new RpcRegistryBeanDefinitionParser());
        registerBeanDefinitionParser("protocol", new RpcProtocolBeanDefinitionParser());
    }
}

4、配置schema和handler

在META-INF目錄下面分別新建spring.handlers和spring.schemas文件。

spring.schemas

http\://www.bytebeats.com/schema/rpc/rpc.xsd=/META-INF/rpc.xsd

spring.handlers

http\://www.bytebeats.com/schema/rpc=com.bytebeats.spring4.extension.xml.RpcNamespaceHandler

5、使用

辛苦這麼大半天,接下來該看看成果了,首先是

applicationContext.xml

<?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:context="http://www.springframework.org/schema/context"
       xmlns:rpc="http://www.bytebeats.com/schema/rpc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.bytebeats.com/schema/rpc http://www.bytebeats.com/schema/rpc/rpc.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="com.bytebeats.spring4.extension.xml"/>

    <rpc:registry id="zk" protocol="zookeeper" address="127.0.0.1" />
    <rpc:protocol id="hessian" name="hessian" port="9001"/>

    <rpc:service id="rpcService" ref="helloService" interface="com.bytebeats.spring4.extension.service.IHelloService" timeout="5000" retries="1"></rpc:service>

    <rpc:ref id="accountService" interface="com.bytebeats.spring4.extension.service.IAccountService" retries="0" check="false" />

    <bean id="helloService" class="com.bytebeats.spring4.extension.service.impl.HelloServiceImpl" />

</beans>

只需要加入 我們自定義的命名空間 即可使用了,如下圖:
這裏寫圖片描述


從Spring 容器中獲取自定義的xml標籤元素,如下:

public class App {

    public static void main( String[] args ) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

        IHelloService helloService = (IHelloService) context.getBean("helloService");
        System.out.println(helloService.sayHello("ricky"));

        RpcServiceBean rpcServiceBean = (RpcServiceBean) context.getBean("rpcService");
        System.out.println("rpcServiceBean:"+rpcServiceBean.getInterfaceName());

        RpcReferenceBean accountService = (RpcReferenceBean) context.getBean("accountService");
        System.out.println("accountService:"+accountService.getInterfaceName());

        RpcRegistryBean rpcRegistryBean = (RpcRegistryBean) context.getBean("zk");
        System.out.println("rpcRegistryBean:"+rpcRegistryBean.getAddress());

        RpcProtocolBean rpcProtocolBean = (RpcProtocolBean) context.getBean("hessian");
        System.out.println("rpcProtocolBean:"+rpcProtocolBean.getPort());

        context.close();
    }
}

點此下載源碼https://github.com/TiFG/spring4-samples/tree/master/spring-ch3-extensible

參考

Spring Extensible XML:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/xml-custom.html

發佈了486 篇原創文章 · 獲贊 405 · 訪問量 296萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章