文章目录
一、Dubbo配置
Dubbo框架直接集成了spring的能力,利用spring配置文件扩展出自定义配置文件解析方式。dubbo定义了自己的配置约束文件,dubbo-config/dubbo-config-spring/src/main/resources/dubbo.xsd,用以解析dubbo标签。
1.dubbo配置文件覆盖策略
dubbo常用的三种配置方式:xml配置,注解,属性文件(properties或yaml)。除了这三种常用的配置方式,还可以通过JVM参数,代码编码的方式配置。
dubbo配置的默认覆盖策略有以下几点原则:
- -D传递给JVM参数优先级最高,比如:-Dubbo.protocol.port=20880
- 代码或xml配置优先级次高,也就是spring的xml文件中的dubbo标签
- properties或yaml配置文件优先级最低,如dubbo.properties,一般可用作配置默认值。只有xml文件没有定义时,dubbo.properties文件的属性才会生效。
2.dubbo 标签
(1) dubbo标签配置项列表
dubbo默认已实现的dubbo标签如下:
标签 | 用途 | 解释 |
---|---|---|
<dubbo:service/> | 服务配置 | 用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心 |
<dubbo:reference/> | 引用配置 | 用于创建一个远程服务代理,一个引用可以指向多个注册中心 |
<dubbo:protocol/> | 协议配置 | 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受 |
<dubbo:application/> | 应用配置 | 用于配置当前应用信息,不管该应用是提供者还是消费者 |
<dubbo:module/> | 模块配置 | 用于配置当前模块信息,可选 |
<dubbo:registry/> | 注册中心配置 | 用于配置连接注册中心相关信息 |
<dubbo:monitor/> | 监控中心配置 | 用于配置连接监控中心相关信息,可选 |
<dubbo:provider/> | 提供方配置 | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选 |
<dubbo:consumer/> | 消费方配置 | 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选 |
<dubbo:method/> | 方法配置 | 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息 |
<dubbo:argument/> | 参数配置 | 用于指定方法参数配置 |
<dubbo:protocol name="jms">
<dubbo:parameter key="queue" value="your_queue" />
</dubbo:protocol>
(2) dubbo标签优先级
这些标签在dubbo应用启动时会通过DubboBeanDefinitionParser
进行解析,将 XML 标签解析为 Bean 对象。这些bean对象有一定的层级关系,多个标签的相同属性可能会被覆盖,这个优先级的原则是:
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
其中,服务方的标签的属性可以通过URL参数的形式传给消费方。Dubbo的参数传递方式统一用URL的形式来实现,注意不是jdk的URL,是dubbo自己定义的URL。
以timeout属性为例,用官网的一张图展示dubbo标签的优先级:
其中<dubbo:provider>
标签属性可以理解为<dubbo:service>
标签属性的默认值,同样,后者会覆盖前者,<dubbo:consumer>
标签属性可以理解为<dubbo:reference>
标签属性的默认值,后者覆盖前者。
当使用缺省值时,是延迟初始化的,只有引用被注入到其它 Bean,或被 getBean() 获取,才会初始化。如果需要饥饿加载,即没有人引用也立即生成动态代理,可以配置init属性,比如:<dubbo:reference ... init="true" />
二、dubbo配置解析原理
下面通过源码介绍配置解析的详细细节。这里只设计最常用的xml配置的方式。
1.dubbo配置解析的代码流程
(1) 配置解析入口
dubbo应用在启动时,首先进行spring配置文件的解析,当遇到dubbo名称空间时,会回调 DubboNamespaceHandler
,该类主要把不同的标签关联到解析实现类中。
该类继承了spring的NamespaceHandlerSupport
类,重写init方法,做主要逻辑,代码如下:
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
可以看到registerBeanDefinitionParser方法将遇到的dubbo标签名交给DubboBeanDefinitionParser
类来处理,具体的解析逻辑就在DubboBeanDefinitionParser
的parse
方法里,DubboBeanDefinitionParser的parse方法是配置解析的核心代码,该方法代码比较长,主要做了以下几件事情:
- 生成并检查spring bean定义BeanDefinition
- 解析具体的标签,注入到BeanDefinition 中
- 两种方式解析标签的属性,注入到标签对应bean中
下面分段来读一下这个parse方法:
关键节点添加了注释:
(2) 生成spring bean定义并检查
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
//生成spring的bean定义,指定beanCLass交给spring反射创建实例
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id");
//确保spring容器没有重复的bean
if ((id == null || id.length() == 0) && required) {
//取name属性值作为bean id
String generatedBeanName = element.getAttribute("name");
if (generatedBeanName == null || generatedBeanName.length() == 0) {
//如果protocol标签没有指定name,则默认用“dubbo”做bean id
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {//其他标签没有指定name属性,则默认使用interface属性作为bean id
generatedBeanName = element.getAttribute("interface");
}
}
if (generatedBeanName == null || generatedBeanName.length() == 0) {
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
//检查重复bean,如果有重复,则生成唯一bean id
while (parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName + (counter++);
}
}
if (id != null && id.length() > 0) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
//每次解析都会向Spring注册新的BeanDefiniton,后续会追加属性
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
/*未完待续*/
前面这一块主要负责把标签解析成对应的bean定义,并注册到spring上下文中,同时保证spring容器中相同id的bean不会被覆盖。
(3) 具体dubbo标签的解析
下面来看具体的像<dubbo:service>
,<dubbo:provider>
,<dubbo:consumer>
等标签的解析,这些标签都会解析成xxxConfig类型,最后实例化,其中<dubbo:service>
和<dubbo:reference>
标签是xxxBean类型,其实也是xxxConfig的子类,因为这两个标签代表具体的服务暴露和服务消费,父类抽象了一些逻辑出来。
/*上接*/
if (ProtocolConfig.class.equals(beanClass)) {
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
if (property != null) {
Object value = property.getValue();
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
} else if (ServiceBean.class.equals(beanClass)) {
//拿到service标签的class属性,也就是服务接口的全限定名
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
//反射生成实例,挂在bean定义上
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
//该方法主要解析标签中的属性,通过键值对的方式提取出来放到bean定义中,spring会自动注入这些属性。
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
} else if (ProviderConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
在解析<dubbo:provider>
和<dubbo:consumer>
时,复用了parseNested方法,主要逻辑是处理内部嵌套标签,因为<dubbo:provider>
标签是可以内部嵌套<dubbo:service>
标签的,同理<dubbo:consumer>
标签可以内部嵌套<dubbo:reference>
标签,该方法逻辑定义了内部标签会持有外部标签的对象,这种设计可以使得内部标签可以直接持有外部标签的属性。也就是<dubbo:service>
可以获取<dubbo:provider>
的属性作为缺省值。
(3) 标签属性的解析和注入
标签的属性提取方式主要分为以下两种:
- 查找配置对象的get,set,is前缀的方法,并通过反射调用方法名与属性名相同的方法,进行注入。
- 如果没有属性名找不到对应的get,set,is前缀方法,则当做parameters参数存储,它是一Map对象。
以上两种方式最终都会将属性作为URL参数存储到dubbo框架的URL中。
DubboBeanDefinitionParser
的parse
方法里,关于属性解析的这片代码过于细节,由于篇幅过长,这里就粘贴出来了。可以自行查看源码。
以上三步操作完成后,parse方法返回一个已经属性注入的spring框架的Beandefinition对象。如果属性是引用对象,dubbo默认会创建RuntimeBeanReference
类型进行注入,运行时由spring来注入引用对象。
其实这里dubbo只做了属性提取,从标签提取到BeanDefinition,运行时注入和转换都又spring来完成。
2.Dubbo标签承载对象继承关系
最后,放上一张dubbo标签承载对象的继承关系图: