Dubbo学习-dubbo自定义配置标签
使用过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),核心的那几个文件:
- dubbo.xsd(dubbo的自定义schema标签文件,里面定义了dubbo所有标签属性)
- spring.schemas(配置spirng自定义schema标签文件位置)
- spring.handlers(配置spirng自定义schema标签处理器类)
- DubboNamespaceHandler.java(spirng自定义schema标签处理器类)
- 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中配置的元素名称一致。
自定义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