前两篇详细讲解了 服务初始化过程的一些细节,分别是:
- 外部化配置初始化过程
- 配置初始化顺序
- 多个配置,先后优先级问题
本篇文章则着重分析服务服务暴露,
可以以以下几个问题进行:
- 在服务暴露中,是否有哪些dubbo功能特性的预制参数会加入到url中呢?
以上问题看完文章后相信大家就可以清楚,若有疑问,关注博主公众号:六点A君,回复标题获取最新答案><
doExport
在检查完配置之后,就会进入 doExport
,当然会判定是否需要延迟加载等情况:
public synchronized void export() {
checkAndUpdateSubConfigs();
if (!shouldExport()) {
return;
}
// 延迟加载
if (shouldDelay()) {
delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
在 doExport
中 ,会判断以下,如果已经暴露过了,那么就不需要再一次暴露,通过 一个 transient volatile boolean
的变量进行判定。
而后调用 doExportUrls
doExportUrls
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
// 拼凑 pathKey
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
// 构建 ProviderModel
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
// 将已发布服务存起来
ApplicationModel.initProviderModel(pathKey, providerModel);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
首先会执行 loadRegistries
,当是是服务端时候,传true,而消费者则是传false,这个方法,主要就是遍历,当地址不是N/A
时候。
- 遍历 所有的 RegistryConfig,将所有的 注册连接
address
拿到。 - 将
application、config、path、protocol
等参数封装为map并加载到url上面 - 通过正则分割address 得到多个url,将url和上面参数拼接
最后并返回。
在 URL.buildKey
中,将从 protocolConfig中获取的path,group,version
进行拼装,最终形式类似于 ${group}/${path}:${version}
而当group
和version
为空是,则直接不会有默认值。path
则不回为空,默认为 全类名。
ProviderModel
则是代表一个已发布的服务。里面会将 interfaceClass
中所有方法 遍历并暂存起来到内部 methods
中,而方法则使用 ProviderMethodModel
表示。
在 ApplicationModel中维护这一个 ConcurrentMap<String, ProviderModel> PROVIDED_SERVICES
,用于存储已经暴露的服务,而 ApplicationModel.initProviderModel
则主要做这件事,将 键值对为 pathKey, providerModel
存到里面。
doExportUrlsFor1Protocol 准备阶段
在做完了前期全部准备工作之后,就要开始主体暴露服务了。
由于涉及配置优先级顺序,dubbo还是执行了一大波 appendParameters
来获取各个不同配置:
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
// 加载多个配置项
appendRuntimeParameters(map);
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
// remove 'default.' prefix for configs from ProviderConfig
// appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, provider);
appendParameters(map, protocolConfig);
appendParameters(map, this);
如上,如果是单配置,则配置优先级是从下往上:
this > protocolConfig > provider > module > application > metrics
而如果支持多配置,即 @Parameter
中 append=true
则会以逗号分割多个配置值。
接下来看对需要暴露的接口方法的处理:
if (CollectionUtils.isNotEmpty(methods)) {
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
// 设置 retries 的值。
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
List<ArgumentConfig> arguments = method.getArguments();
if (CollectionUtils.isNotEmpty(arguments)) {
for (ArgumentConfig argument : arguments) {
// convert argument type
// 说明有参数
if (argument.getType() != null && argument.getType().length() > 0) {
Method[] methods = interfaceClass.getMethods();
// 使用反射,获取所有interface的所有方法
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// 找到了,就是这个方法,它配置了dubbo 的arguments,从而判断是一个callback,还是多个callback
if (methodName.equals(method.getName())) {
Class<?>[] argtypes = methods[i].getParameterTypes();
// 1个callback
if (argument.getIndex() != -1) {
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
} else {
// 多个callback
for (int j = 0; j < argtypes.length; j++) {
Class<?> argclazz = argtypes[j];
if (argclazz.getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
}
}
}
}
}
}
} else if (argument.getIndex() != -1) {
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
}
} // end of methods for
}
上文代码中,主要是增加对callback 的支持。
- 首先会对配置而来的
List<MethodConfig>
进行检测,看是否配置了方法级别的参数 - 获取
interface
里面方法集合,找出配置了的 argument参数的方法 - 如果有,则会进行初始化
callback
参数
过完上面的 callback参数解析之后,会判断是否为 generic
,如果不是 generic
泛化调用,会加入增加诸如 reversion
// 判断是否为 generic 泛化调用
if (ProtocolUtils.isGeneric(generic)) {
map.put(GENERIC_KEY, generic);
map.put(METHODS_KEY, ANY_VALUE);
} else {
// 版本号
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
// 通过 Dubbo字节码生成器获取所有的 方法
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
// 将方法以逗号分割形式加入到map中。
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
以上代码判断了是否为泛化调用,以及加入了版本号, 所有方法参数等。
如果有token,则会将token也加入到map中。
doExportUrlsFor1Protocol 开始export
// 获取配置的ip,并连接绑定端口
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
// 将ip端口,以及map,以及 path 拼凑成url
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
// 获取 动态配置中心,并将url写道 配置中心
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
后面还会检查 SCOPE_KEY
,从而判断是否应该export。
后续仍然会加入 dynamic
和 monitor
等信息。
而后通过 ProxyFactory
生成代理类,并由包装类 DelegateProviderMetaDataInvoker
再执行 export
方法:
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
在ServiceConfig
的 代理工厂使用的是默认的代理工厂:
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
即 JavassistProxyFactory
代理类。
最终通过 ref、interface、url
等信息,生成了 Invoker 返回。
而由 DelegateProviderMetaDataInvoker
包装了一层之后,调用 protocol.export
进行暴露。默认是 DubboProtocol
的 export
进行调用。
DubboProtocol 的 export
在export
方法里面,首先判断了对 stub
特性支持,stub
即本地存根,http://dubbo.apache.org/en-us/docs/user/demos/local-stub.html
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// 构造一个Exporter
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
// 增加对Exporter支持
Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
openServer(url);
optimizeSerialization(url);
return exporter;
}
而后会调用 openServer
,其主要作用是开启一个 ExchangeServer
, 实际就是对 底层网络通信的封装,在dubbo
传输协议中,使用的是Netty
作为网络传输协议,Dubbo 内置也支持 Grizzly、Mina
等传输协议。
而 optimizeSerialization
则是 序列化优化器来优化url,不过目前版本(2.7.2)并没有实现其相关逻辑, SerializationOptimizer
没有子类。
最后返回该 exporter
关于 ExchangeServer
, 将会以专门章节分析。
总结
服务端服务暴露中,主要做了以下几件事:
- 加载 相关 配置
- 设置dubbo配置中一些特性,如
callback
, 并判断是否为generic
泛化接口 - 将 dubbo 方法及配置调用封装成 URL
- 如果有配置中心
ConfiguratorFactory
,则将url信息配置到 配置中心,具体需要看AbstractConfigurator
继承者逻辑,默认的dubbo
则不需要配置 - 启动 网络传输层 服务,即
ExchangeServer
下一章 则进入 Dubbo 消费者服务分析,当然服务消费者和服务提供者会有很多类似的逻辑,这点看 dubbo 的 config 代码复用性就可以略知一二。
觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路: