在上一篇文章中,主要用了 dubbo-samples的一个简单例子,来跑通了Dubbo例子,那么这一篇文章中,将以上一篇文章为基础,研究下Dubbo Provider是如何跑起来的。
本文以以下几个问题展开:
- Dubbo 是 以什么为单位 对外提供服务的?
- Dubbo 初始化各个Config的配置是怎么样的过程呢?
- Dubbo 在获取配置过程中,有什么优先级?
以上问题看完文章后相信大家就可以清楚,若有疑问,关注博主公众号:六点A君,回复标题获取最新答案><
ServiceConfig
以下思路是先从设置字段变量,然后再关注export方法入手:
变量设置
先把代码找过来:
ServiceConfig<HelloService> service = new ServiceConfig<>();
service.setApplication(new ApplicationConfig("dubbo-provider"));
service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
service.setInterface(HelloService.class);
service.setRef(new HelloServiceImpl());
service.export();
代码主要围绕ServiceConfig
展开,上面代码就是设置一些ServiceConfig的属性,例如 ApplicationConfig
、RegisterConfig
、Interface
以及ref
整个ServiceConfig,除了顶层的AbstractConfig是实现Serializable接口外,其他都是一个继承体系,也就是说,ServiceConfig拥有上面4个类的所有字段信息(当然需要时可见的字段信息)。
翻阅代码,发现 ApplicationConfig
和 RegisterConfig
都是在 AbstractInterfaceConfig中,但是他们却又不同,Application只能有一个,而RegisterConfig则是用List存储的,所以也验证了Dubbo注册中心可以有多个。
// 设置 application
public void setApplication(ApplicationConfig application) {
ConfigManager.getInstance().setApplication(application);
this.application = application;
}
// 设置 RegisterConfig
public void setRegistry(RegistryConfig registry) {
List<RegistryConfig> registries = new ArrayList<RegistryConfig>(1);
registries.add(registry);
setRegistries(registries);
}
而 Interface
和 ref
则是在 ServiceConfig
中,其中, Interface只能为接口,否则会报错,而ref则是对应ServiceConfig的泛型变量。
export
从名字来看,就是暴露服务,那么是一个怎么样的暴露过程呢?
整个export是一个 synchronized
方法,但是这是否会影响效率呢?不过这个方法调用频率并不高,另一方面Java8以后虚拟机对 synchronized
关键字做了充分优化,这可能就是 Dubbo 选择使用 方法级别原因?
里面有两个要做的:
- 检查并填充配置
- 暴露服务
检查配置是在 checkAndUpdateSubConfigs
中完成的
即检查并且更新一些配置。
checkAndUpdateSubConfigs
在 checkAndUpdateSubConfigs
中,是一大段代码,里面包括对ServiceConfig中其他字段初始化,以及从注册中心中获取一些配置等,
本届主要细说 completeCompoundConfigs
以及 startConfigCenter
completeCompoundConfigs
这个方法是放在第一,主要是绑定一些指定的配置,但是在ServiceConfig并没有同步的配置,例如 application, module, registries, monitor, protocols, configCenter
等,但是里面有优先级,在 registries, monitor
的设置上具有优先级,
即 ProviderConfig
> ModuleConfig
> ApplicationConfig
。
而这样的优先级方法,则是通
== null
实现,即如果已经被设定了,就不会再次设定了。
startConfigCenter
在 startConfigCenter
里面,首先尝试设定 当前 AbstractInterfaceConfig
子类的值,即如果AbstractInterfaceConfig
中 configCenter
不为空,就设置当前子类的 configCenter
值, 在本例中就是 ServiceConfig的
。
然后,如果 ConfigCenter
不为空,那么就尝试 refresh
一下,从而从新获取配置。
以及 执行 prepareEnviroment
方法
最后,将会由配置管理器 ConfigManager
执行 refreshAll
方法,而这个方法 将 ApplicationConfig, MonitorConfig, ModuleConfig, ProtocolConfig, RegistryConfig, ProviderConfig, ConsumerConfig
会都执行 refresh
方法。
这里只执行 7 个 config的refresh方法,而不是执行所有的 refresh方法
因为 configCenter
的初始化被分开了,并且当 configCenter != null
时候,除了 执行 this.configCenter.refresh();
还会执行 prepareEnvironment
, 而 prepareEnviroment
则是进行一些前期环境检查工作。而对于 可配置的 多类型注册中心,则是在这里面进行设定的。
在 ConfigManager
中,
ConfigManager
在ConfigManager
里面,主要 负责维护以下几个config
private ApplicationConfig application;
private MonitorConfig monitor;
private ModuleConfig module;
private ConfigCenterConfig configCenter;
private Map<String, ProtocolConfig> protocols = new ConcurrentHashMap<>();
private Map<String, RegistryConfig> registries = new ConcurrentHashMap<>();
private Map<String, ProviderConfig> providers = new ConcurrentHashMap<>();
private Map<String, ConsumerConfig> consumers = new ConcurrentHashMap<>();
从上面定义可知, 一个应用可以有多个 ProtocolConfig, RegistryConfig, ProviderConfig, ConsumerConfig
等配置,而其他的只能有一个。
并且 ConfigManager属于饿汉式的单例模式,有兴趣可以看: 单例模式之饿汉模式(立即加载)
refresh
refresh方法主要是获取当前config下最新配置,将系统配置,或者外部化配置设置到config中。
public void refresh() {
try {
// 获取 多个混合配置
CompositeConfiguration compositeConfiguration = Environment.getInstance().getConfiguration(getPrefix(), getId());
InmemoryConfiguration config = new InmemoryConfiguration(getPrefix(), getId());
// 将当前类的 信息放入到配置中
config.addProperties(getMetaData());
// 看 isConfig的顺序,从而加载不同配置
if (Environment.getInstance().isConfigCenterFirst()) {
// The sequence would be: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
compositeConfiguration.addConfiguration(3, config);
} else {
// The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
compositeConfiguration.addConfiguration(1, config);
}
// 循环,填充参数
Method[] methods = getClass().getMethods();
for (Method method : methods) {
if (MethodUtils.isSetter(method)) {
try {
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
// isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) {
method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value));
}
} catch (NoSuchMethodException e) {
logger.info("Failed to override the property " + method.getName() + " in " +
this.getClass().getSimpleName() +
", please make sure every property has getter/setter method provided.");
}
}
}
} catch (Exception e) {
logger.error("Failed to override ", e);
}
}
在上一段代码中,首先会 Environment.getInstance().getConfiguration(getPrefix(), getId());
会去获取 4个渠道里面所有的关于 getPrefix, getId
的配置,如下在 Environment.getConfiguration
中代码:
public CompositeConfiguration getConfiguration(String prefix, String id) {
CompositeConfiguration compositeConfiguration = new CompositeConfiguration();
// Config center has the highest priority
compositeConfiguration.addConfiguration(this.getSystemConfig(prefix, id));
compositeConfiguration.addConfiguration(this.getAppExternalConfig(prefix, id));
compositeConfiguration.addConfiguration(this.getExternalConfig(prefix, id));
compositeConfiguration.addConfiguration(this.getPropertiesConfig(prefix, id));
// 最后封装返回复合配置
return compositeConfiguration;
}
为什么要将四个渠道配置都获取下来呢?
主要有以下两个原因:
- 获取所有的配置,尽量保证如果写了配置,那么就需要填充上
- 形成 有序链,从而有利于后面的优先级获取
而在后一端代码:
InmemoryConfiguration config = new InmemoryConfiguration(getPrefix(), getId());
config.addProperties(getMetaData());
则是为了将metaData数据加入,而在 getMetaData
中,则 使用反射,将 遍历所有的 get*
的获取基本类型方法,从而获取已有的配置里面的所有的字段值。
另一方面,如果有 getParameters
,也同样回获取 getParameters
的键值对,将其一并返回,这样依赖在
另一方面最后以Map返回。
配置优先级
配置优先级关键就是在以下代码:
if (Environment.getInstance().isConfigCenterFirst()) {
// The sequence would be: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
compositeConfiguration.addConfiguration(3, config);
} else {
// The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
compositeConfiguration.addConfiguration(1, config);
}
由于上一小节中,了解到 compositeConfiguration
中,会包括所有地方的配置,主要是4个地方:
SystemConfiguration ,AppExternalConfiguration , ExternalConfiguration , PropertiesConfiguration
而刚刚从本类中的配置获取到的 InmemoryConfiguration
,则是由配置,决定 它的优先级,默认下是 配置中心优先级要高于 外部化配置。
设置变量
最后,如何来设定变量呢?
即拿到了配置,就到了该要设定配置的时候了,Dubbo给了一个循环,将 set*
方法拿到,再有上一步获取的配置去配置值。
for (Method method : methods) {
if (MethodUtils.isSetter(method)) {
try {
// 获取值,
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
// isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) {
method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value));
}
} catch (NoSuchMethodException e) {
logger.info("Failed to override the property " + method.getName() + " in " +
this.getClass().getSimpleName() +
", please make sure every property has getter/setter method provided.");
}
}
}
这一段代码中,主要通过 compositeConfiguration.getString(extractPropertyName(getClass(), method))
获取配置,而里面则是遍历 compositeConfiguration
的 配置list,而由于优先级先后,所以就有了上一步的优先级定义。
当以配置中心优先时候,当前配置字段生效顺序是:
SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
而当不是配置中心优先时候,配置生效顺序则为:
SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
而 AbstractConfig,则是程序员自己在程序中,或者是在yml或者xml等配置文件中配置。
为了避免篇幅太大,导致阅读疲劳,下一篇则继续分析 checkAndUpdateSubConfigs
其他部分,以及结合 ConfigCenter
示例 看Dubbo具体如何操作配置优先级的。
关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路: