1.Dubbo服务架构图
名词解释
Provider:暴露服务的服务提供方
Container:服务运行容器(可以理解为Spring容器)
Registry:服务注册与发现的注册中心
Consumer:调用远程服务的消费者(一般为Web应用)
Monitor:统计服务的调用次数喝调用时间的监控中心
Container详解
Dubbo的Container是一个独立的容器,因为Dubbo服务通常不需要部署在Web容器(如Tomcat、JBoss等)中,没有必要用Web容器去加载服务,服务容器只是一个简单的Main方法,并且在一个简单的Spring容器用于暴露服务。
org.apache.dubbo.container.Container是服务启动的主类源码:
package org.apache.dubbo.container;
import org.apache.dubbo.common.extension.SPI;
/**
* Container. (SPI, Singleton, ThreadSafe)
*/
@SPI("spring")
public interface Container {
/**
* start method to load the container.
*/
void start();
/**
* stop method to unload the container.
*/
void stop();
}
通过以上代码可以看到,这个接口有两个方法,start()方法和stop()方法,它的实现类有Log4jContainer、LogBackContainer和SpringContainer,由于该接口上有@SPI(“spring”)注解,所以默认调用SpringContainer。
那么接下来看一下SpringContainer的源码:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.container.spring;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.container.Container;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* SpringContainer. (SPI, Singleton, ThreadSafe)
*
* The container class implementation for Spring
*/
public class SpringContainer implements Container {
/**读取dubbo.properties配置文件中dubbo.spring.config”的参数*/
public static final String SPRING_CONFIG = "dubbo.spring.config”;
/**默认加载的Spring配置文件路径*/
public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
static ClassPathXmlApplicationContext context;
public static ClassPathXmlApplicationContext getContext() {
return context;
}
@Override
public void start() {
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
/**如果参数为空,则默认加载META-INF/spring路径下的所有配置文件*/
if (StringUtils.isEmpty(configPath)) {
configPath = DEFAULT_SPRING_CONFIG;
}
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"), false);
context.refresh();
//加载完配置文件之后,Spring容器开始启动
context.start();
}
@Override
public void stop() {
try {
if (context != null) {
context.stop();
context.close();
context = null;
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}
容器在启动时,先去读取dubbo.properties文件,如果dubbo.properties文件中没有配置dubbo.spring.config 参数,则默认加载META-INF/spring下的全部Spring配置文件。
SpringContainer加载完配置文件之后,Spring容器开始启动,此时Dubbo服务的整个启动过程结束。
2. Dubbo-provider服务的配置文件:
由图2我们可以看出Dubbo服务的项目结构图,在resources目录下,重点需要两个配置文件:
1、dubbo-consumer.xml
2、dubbo.properties
在项目启动时,Spring容器会加载这两个配置文件,Dubbo框架中的dubbo-config模块会解析这些配置文件,并且解析成对应的Bean定义,并注册到Spring上下文中。那么接下来我们分析,这些配置文件到底是怎么被解析成对应的Bean,并且注册到Spring的上下文当中的。
3.Dubbo服务的启动类:
想要了解配置文件被加载的过程,那么我们首先从项目启动的入口着手进行分析。接下来,我们看下Dubbo服务启动类的代码:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.demo.provider;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Application {
public static void main(String[] args) throws Exception {
/**创建Spring IOC容器*/
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
System.in.read();
}
}
启动类的关键代码,加载Sring的IOC容器,并且指定dubbo的配置文件,那么当Spring容器启动的时候,会加载dubb-provider.xml文件。
关于ClassPathXmlApplicationContext的加载过程有篇博客介绍的非常详细,博客地址: ClassPathXmlApplicationContext加载过程 那么当Spring容器加载完dubbo.provider.xml文件之后,又怎么将配置文件解析并且获取到里面的一些参数呢,接下来我们看一下dubbo-config模块的代码,看下具体解析。
4.Dubbu服务配置文件的加载过程
首先我们来看一下dubbo-config模块的整体架构:
如图3所示,dubbo-config模块分为两块,一个是dubbon-config-api和dubbo-config-spring,其中dubbo-config-api提供了一些接口和封装了一些实体类,dubbo-config-spring重点实现的是在spring容器启动时,加载并且解析dubbo项目的配置文件。
基于schema设计解析:
dubbo-config-spring的META-INF下有三个配置文件: dubbo.xsd、spring.handlers、spring.schemas文件。那么我们分别看下这三个配置文件的作用:
dubbo.xsd文件:规范了在编写xml文件时需要有哪些元素,以及元素的节点是什么,也就是对我们的provider服务中的dubbo-provider.xml文件做了一个约束。
spring.schemas文件:
http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd
文件中指定了dubbo的约束文件,Spring框架通过spring.handlers中的配置来解析用户的自定义配置,而dubbo.xsd文件正是用户自定义的一种约束规范,所以在此指定dubbo.xsd文件 Spring就可以解析xsd文件中配置的节点。
spring.handlers文件:
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
指定了解析配置文件中标签的解析类。
Spring解析项目中dubbo-provider.xml的过程可以这么理解:
1、加载dubbo-provider.xml
2、解析到自定义的namespace(如 <dubbo:service>标签)时查找对应的spring.schemas和spring.handlers文件
3、spring.schemas文件指定了约束文件,spring.handlers指定了解析标签的类及DubboNamespaceHandler来进行初始化和解析。
dubbo.xsd文件中定义了很多模块,这些模块基本可以满足大多数使用场景。
基于XML配置解析原理:
通过以上分析我们可以得知,最终解析xml文件的是DubboNamespaceHandler这个类,接下来我们看下DubboNamespaceHandler的实现:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.config.spring.schema;
import org.apache.dubbo.common.Version;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.MetadataReportConfig;
import org.apache.dubbo.config.ModuleConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.MonitorConfig;
import org.apache.dubbo.config.MetricsConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.spring.ConfigCenterBean;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.config.spring.ServiceBean;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* DubboNamespaceHandler
*
* @export
*/
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@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("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.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());
}
}
DubboNamespaceHandler的继承图:
DubboNamespaceHandler集成了NamespaceHandlerSupport,因此不需要实现全部的解析工作,只需要将自定义schema中的元素解析器注册进来就可以。
DubboBeanDefinitionParser类实现了BeanDefinitionParser这个接口,负责将标签转换成bean定义对象BeanDefinition。
我们接下来看一下DubboBeanDefinitionParser的parser方法解析步骤:
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
/**初始化RootBeanDefinition,生成Spring的bean定义,指定beanClass交给Spring反射创建实例*/
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id”);
/**确保Spring容器中没有重复的Bean定义*/
if (StringUtils.isEmpty(id) && required) {
/**依次尝试获取XML文件配置标签的name和interface属性做为Bean的唯一Id*/
String generatedBeanName = element.getAttribute("name");
if (StringUtils.isEmpty(generatedBeanName)) {
/**如果协议中没有指定名称,则默认为Dubbo*/
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {
generatedBeanName = element.getAttribute("interface");
}
}
if (StringUtils.isEmpty(generatedBeanName)) {
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
while (parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName + (counter++);
}
}
if (StringUtils.isNotEmpty(id)) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
/**每次解析回想Sring注册心的BeanDefinition,后续会追加属性*/
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
/**<dubbo:protocol>标签解析*/
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));
}
}
}
}
/**<dubbo:service>标签解析*/
else if (ServiceBean.class.equals(beanClass)) {
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
}
/**<dubbo:provider>标签解析*/
else if (ProviderConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
}
/**<dubbo:consumer>标签解析*/
else if (ConsumerConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
Set<String> props = new HashSet<>();
ManagedMap parameters = null;
for (Method setter : beanClass.getMethods()) {
String name = setter.getName();
if (name.length() > 3 && name.startsWith("set")
&& Modifier.isPublic(setter.getModifiers())
&& setter.getParameterTypes().length == 1) {
Class<?> type = setter.getParameterTypes()[0];
String beanProperty = name.substring(3, 4).toLowerCase() + name.substring(4);
String property = StringUtils.camelToSplitName(beanProperty, "-");
props.add(property);
// check the setter/getter whether match
Method getter = null;
try {
getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e) {
try {
getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e2) {
// ignore, there is no need any log here since some class implement the interface: EnvironmentAware,
// ApplicationAware, etc. They only have setter method, otherwise will cause the error log during application start up.
}
}
if (getter == null
|| !Modifier.isPublic(getter.getModifiers())
|| !type.equals(getter.getReturnType())) {
continue;
}
if ("parameters".equals(property)) {
parameters = parseParameters(element.getChildNodes(), beanDefinition);
} else if ("methods".equals(property)) {
parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
} else if ("arguments".equals(property)) {
parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
} else {
String value = element.getAttribute(property);
if (value != null) {
value = value.trim();
if (value.length() > 0) {
if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
beanDefinition.getPropertyValues().addPropertyValue(beanProperty, registryConfig);
} else if ("provider".equals(property) || "registry".equals(property) || ("protocol".equals(property) && ServiceBean.class.equals(beanClass))) {
/**
* For 'provider' 'protocol' 'registry', keep literal value (should be id/name) and set the value to 'registryIds' 'providerIds' protocolIds'
* The following process should make sure each id refers to the corresponding instance, here's how to find the instance for different use cases:
* 1. Spring, check existing bean by id, see{@link ServiceBean#afterPropertiesSet()}; then try to use id to find configs defined in remote Config Center
* 2. API, directly use id to find configs defined in remote Config Center; if all config instances are defined locally, please use {@link org.apache.dubbo.config.ServiceConfig#setRegistries(List)}
*/
beanDefinition.getPropertyValues().addPropertyValue(beanProperty + "Ids", value);
} else {
Object reference;
if (isPrimitive(type)) {
if ("async".equals(property) && "false".equals(value)
|| "timeout".equals(property) && "0".equals(value)
|| "delay".equals(property) && "0".equals(value)
|| "version".equals(property) && "0.0.0".equals(value)
|| "stat".equals(property) && "-1".equals(value)
|| "reliable".equals(property) && "false".equals(value)) {
// backward compatibility for the default value in old version's xsd
value = null;
}
reference = value;
} else if(ONRETURN.equals(property) || ONTHROW.equals(property) || ONINVOKE.equals(property)) {
int index = value.lastIndexOf(".");
String ref = value.substring(0, index);
String method = value.substring(index + 1);
reference = new RuntimeBeanReference(ref);
beanDefinition.getPropertyValues().addPropertyValue(property + METHOD, method);
} else {
if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
if (!refBean.isSingleton()) {
throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
}
}
reference = new RuntimeBeanReference(value);
}
beanDefinition.getPropertyValues().addPropertyValue(beanProperty, reference);
}
}
}
}
}
}
NamedNodeMap attributes = element.getAttributes();
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
Node node = attributes.item(i);
String name = node.getLocalName();
if (!props.contains(name)) {
if (parameters == null) {
parameters = new ManagedMap();
}
String value = node.getNodeValue();
parameters.put(name, new TypedStringValue(value, String.class));
}
}
if (parameters != null) {
beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
}
return beanDefinition;
}
Dubbo通过parse方法解析配置文件,并将配置文件的各个参数映射到对应的JavaBean。
我是割草的小猪头,不断学习,不断进步,后续陆续更新Dubbo系列的文章,如您有兴趣一起了解,欢迎关注,如文章中有不妥之处,欢迎指正!