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" />

    
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    
    <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配置机制实践_第1张图片

最后,我们也可以在Spring配置文件引入我们自定义的标签了,如下:


<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,如下:


<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


<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配置机制实践_第2张图片


从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

你可能感兴趣的:(Spring,Framework)