使用过dubbo的朋友都知道,dubbo有很多自定义的配置标签,比如<dubbo:service />、<dubbo:reference />等。那么这些自定义是怎么实现的呢?

       dubbo是运行在spring容器中,dubbo的配置文件也是通过spring的配置文件applicationContext.xml来加载,所以dubbo的自定义配置标签实现,其实是依赖spring的xml schema机制,下面来看一下dubbo的实现过程。


上图是dubbo实现自定义配置标签的源码工程(git地址:https://github.com/apache/incubator-dubbo/tree/master/dubbo-config/dubbo-config-spring),核心的那几个文件:

  1. dubbo.xsd(dubbo的自定义schema标签文件,里面定义了dubbo所有标签属性)
  2. spring.schemas(配置spirng自定义schema标签文件位置)
  3. spring.handlers(配置spirng自定义schema标签处理器类)
  4. DubboNamespaceHandler.java(spirng自定义schema标签处理器类)
  5. DubboBeanDefinitionParser.java(spirng自定义schema标签解析类)

-----------------------------------------------------------------------------------------------------------------------------

接下来我参考了dubbo源码,自己实现了一个简单的自定义配置标签项目,希望能帮到大家。先上代码结构图(源码地址在:https://gitee.com/plg17/plg-dubbo/tree/master/dubbo_common/CustomNamespace):

1、自定义配置标签属性bean类(ConsumerConfig.java)

   ConsumerConfig.java是一个不同的java bean类,其中定义了本次自定义标签中所需要的属性,就像dubbo中的配置<dubbo:service />,有interface、ref、version等等配置项。自定义schema标签配置文件中的属性要跟ConsumerConfig.java中定义的属性一直,否则会报错。

/**
 * 自定义schema属性bean
 * 
 * @author Administrator
 *
 */
public class ConsumerConfig implements Serializable {
    private static final long serialVersionUID = -5368563657151220211L;

    // id
    protected String id;
    
    // timeout for remote invocation in milliseconds
    protected Integer timeout;

    // retry times
    protected Integer retries;

    // max concurrent invocations
    protected Integer actives;
2、自定义schema标签文件(myCustom.xsd)

  自定义shema标签文件,名字可以随便取,后缀为.xsd(xsd文件规范可以参考http://blog.sina.com.cn/s/blog_ad0672d60102uy6w.html)。

<?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://gitee.com/plg17/plg-dubbo/myCustom" targetNamespace="http://gitee.com/plg17/plg-dubbo/myCustom">
	
	<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
	<xsd:import namespace="http://www.springframework.org/schema/beans" />
	<xsd:import namespace="http://www.springframework.org/schema/tool" />

	<xsd:complexType name="consumerType">
		<xsd:attribute name="id" type="xsd:string" use="optional" default="">
			<xsd:annotation>
				<xsd:documentation>
                    <![CDATA[ The exclusive id. ]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="timeout" type="xsd:string" use="optional" default="0">
			<xsd:annotation>
				<xsd:documentation><![CDATA[ The method invoke timeout. ]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="retries" type="xsd:string" use="optional" default="1">
			<xsd:annotation>
				<xsd:documentation><![CDATA[ The method retry times. ]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="actives" type="xsd:string" use="optional" default="100">
			<xsd:annotation>
				<xsd:documentation><![CDATA[ The max active requests. ]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
	</xsd:complexType>

    <xsd:element name="consumer" type="consumerType">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ Export service default config ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>
    
</xsd:schema>

注意红色标注的地方:

1. xmlns="http://gitee.com/plg17/plg-dubbo/myCustom" >:xml命名空间,普遍都命名成url的形式,但终究不是url,不要求能访问。这个名字空间会在spring的applicationContext.xml配置文件中用到。

2. "id"、"timeout"、"retries"、"actives":这里的属性一定是ConsumerConfig.java中的属性的子集,如果xsd文件中的属性不存在与ConsumerConfig.java,那启动的时候回报错。

3. <xsd:element name="consumer" type="consumerType">:这里的consumerType和<xsd:complexType name="consumerType">一样,是一种映射关系的配置。而consumer则是自定义标签的元素名称,跟MyNamespaceHandler.java中配置的元素名称一致。

3、spirng自定义schema标签处理器类(MyNamespaceHandler.java

    自定义schema标签的处理器,继承于org.springframework.beans.factory.xml.NamespaceHandlerSupport。用于处理自定义标签的命名空间。

package com.plg.dubbo.customschema;

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

/**
 * 自定义schema标签的处理器
 * 
 * @author Administrator
 *
 */
public class MyNamespaceHandler extends NamespaceHandlerSupport {
    /**
     * 注册自定义标签的命名空间
     */
    @Override
    public void init() {
        // 格式:registerBeanDefinitionParser(自定义标签的命名空间, spring容器的bean对象实例)
        // 这里的自定义标签元素名称:"consumer",跟applicationContext.xml中的配置<myCustom:consumer />一致
        registerBeanDefinitionParser("consumer", new MyCustomBeanDefinitionParser(ConsumerConfig.class, true));
    }
}
4、spirng自定义schema标签解析类(MyCustomBeanDefinitionParser.java

        自定义标签解析类,实现org.springframework.beans.factory.xml.BeanDefinitionParser接口。实现将自定义的标签封装成spring内部的bean对象RootBeanDefinition,并注册到spring容器中。

package com.plg.dubbo.customschema;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

/**
 * 自定义schema标签的解析类
 * 
 * @author Administrator
 *
 */
public class MyCustomBeanDefinitionParser implements BeanDefinitionParser {
    private static final Logger logger = LoggerFactory.getLogger(MyCustomBeanDefinitionParser.class);

    private final Class<?> beanClass;
    private final boolean required;

    public MyCustomBeanDefinitionParser(Class<?> beanClass, boolean required) {
        this.beanClass = beanClass;
        this.required = required;
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        return parse(element, parserContext, beanClass, required);
    }

    /**
     * 解析自定义schema文件,并出注册到spring容器中
     * 
     * @param element
     *            xml配置文件中的配置项
     * @param parserContext
     *            spring上下文
     * @param beanClass
     *            自定义schema标签对应的java bean文件
     * @param required
     *            是否必须(dubbo中有些配置不是必须的)
     * @return
     */
    private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
        String id = element.getAttribute("id");
        String timeout = element.getAttribute("timeout");
        String retries = element.getAttribute("retries");
        String actives = element.getAttribute("actives");

        if (!StringUtils.isEmpty(id)) {
            // 重复spring bean校验
            if (parserContext.getRegistry().containsBeanDefinition(id)) {
                logger.warn("重复spring bean id,id={}", id);
                throw new IllegalStateException("Duplicate spring bean id " + id);
            }
        } else {
            logger.warn("spring bean id不能为null");
            throw new IllegalStateException("spring bean id can not be null");
        }

        // 把bean封装成RootBeanDefinition对象,RootBeanDefinition继承了BeanDefinition
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);// 设置这个bean是否延迟初始化,false-启动时就初始化
        beanDefinition.getPropertyValues().addPropertyValue("id", id);
        if (!StringUtils.isEmpty(timeout)) {
            beanDefinition.getPropertyValues().addPropertyValue("timeout", timeout);
        }
        if (!StringUtils.isEmpty(retries)) {
            beanDefinition.getPropertyValues().addPropertyValue("retries", retries);
        }
        if (!StringUtils.isEmpty(actives)) {
            beanDefinition.getPropertyValues().addPropertyValue("actives", actives);
        }

        // 把RootBeanDefinition bean对象注册到spring容器中
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);

        return beanDefinition;
    }
}
5、spring.handlers和spring.schemas

        spring.handlers和spring.schemas,扩展spring schema标签要求要有这两个配置文件,名字不可修改,spring默认会去加载。

spring.schemas:配置自定义schema标签文件的位置

http\://gitee.com/plg17/plg-dubbo/myCustom.xsd=META-INF/myCustom.xsd

spring.handlers:配置自定义标签的处理器类

http\://gitee.com/plg17/plg-dubbo/myCustom=com.plg.dubbo.customschema.MyNamespaceHandler
6、spring配置文件(applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context" 
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:myCustom="http://gitee.com/plg17/plg-dubbo/myCustom"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
 	http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
 	http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://gitee.com/plg17/plg-dubbo/myCustom
    http://gitee.com/plg17/plg-dubbo/myCustom.xsd">

	<!-- 
		<myCustom:consumer />
		1.myCustom:跟本文件中的xmlns:myCustom一致
		2.consumer:跟MyNamespaceHandler.java中定义的自定义标签元素名称要一致
	 -->
	<myCustom:consumer id="consumer" timeout="12000" retries="1" actives="100" />

</beans>

注意红色标注部分:

1.url格式的配置就是spring.handlers和spring.schemas中配置的命名空间名称了。

2.id、timeout、retries、acrives就是ConsumerConfig.java中的属性。

3.<myCustom:consumer />,myCustom是配置文件中的命名空间的key,consumer跟MyNamespaceHandler.java中定义的自定义标签元素名称一致。

7、spring启动器和测试结果(Main.java)
package com.plg.dubbo.customschema;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 启动类
 * 
 * @author Administrator
 *
 */
public final class Main {
    private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);

    private Main() {
        throw new IllegalAccessError("Utility class");
    }

    /**
     * @param args
     */
    @SuppressWarnings("resource")
    public static void main(String[] args) {
        try {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:/META-INF/applicationContext.xml");
            ConsumerConfig config = (ConsumerConfig) context.getBean("consumer");
            System.out.println("id = " + config.getId());
            System.out.println("timeout = " + config.getTimeout());
            System.out.println("actives = " + config.getActives());
            System.out.println("retries = " + config.getRetries());
        } catch (Exception e) {
            LOGGER.error("运行异常", e);
        }
    }
}

启动spring容器,并输出自定义配置中的属性值,执行结果:


欧了!

8、注意

        如果applicationContext.xml配置文件中的自定义标签配置中出现的属性不包含在ConsumerConfig.java,那就会报错,如把ConsumerConfig.java中的id属性删掉,启动spring容器时就报异常:

[org.springframework.context.support.ClassPathXmlApplicationContext] - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@dcf3e99: startup date [Sat Apr 14 20:58:24 CST 2018]; root of context hierarchy
[org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from URL [file:/D:/JAVA/workspace(learing)/plg-dubbo/dubbo_common/CustomNamespace/target/classes/META-INF/applicationContext.xml]
[com.plg.dubbo.customschema.Main] - 运行异常
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 13 in XML document from URL [file:/D:/JAVA/workspace(learing)/plg-dubbo/dubbo_common/CustomNamespace/target/classes/META-INF/applicationContext.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 13; columnNumber: 79; cvc-complex-type.3.2.2: 元素 'myCustom:consumer' 中不允许出现属性 'id'。
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:399)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:252)
	at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:127)
	at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:93)
	at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129)
	at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:537)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:452)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
	at com.plg.dubbo.customschema.Main.main(Main.java:26)
Caused by: org.xml.sax.SAXParseException; lineNumber: 13; columnNumber: 79; cvc-complex-type.3.2.2: 元素 'myCustom:consumer' 中不允许出现属性 'id'。
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:396)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:284)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:452)
	... 14 more


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐