开头
上一节讲到了dubbo是如何和spring进行集成的,里面讲解了如何加载@Service和@Reference注解,这一节开始讲dubbo的核心源码-服务导出,也就是说,服务端要将自己的接口暴露,解析为一个可供消费端调用的远程服务。
主要流程
1.入口,ServiceBean.onApplicationEvent,ServiceBean监听了spring启动事件,启动完后会调用此方法进行服务导出
2.配置参数准备,如优先级、覆盖等,因为配置参数可以在消费端和服务端同时配置,所以需要合并、取优先级等
3.根据服务的url、参数、协议等信息启动通信服务器,如tomcat、netty等
4.将服务信息注册到注册中心-register protocol
5.监听路径registry.subscribe
1.总入口
上一节讲到了,spring集成dubbo的时候,服务端的bean会生成一个ServiceBean,这个ServiceBean实现了ApplicationListener接口,在spring启动完后会调用onApplicationEvent方法
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
// 服务导出很重要(服务注册)
export();
}
}
2.配置参数准备
在export方法中,第一行就是配置参数检查和修改 checkAndUpdateSubConfigs();参数很多,就不点进去看了
public synchronized void export() {
checkAndUpdateSubConfigs();
if (!shouldExport()) {
return;
}
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 导出服务
doExport();
}
}
3.根据服务的url、参数、协议等信息启动通信服务器,如tomcat、netty等
从2方法点进去doExport->doExportUrls->doExportUrlsFor1Protocol(重点),来到这个方法的最后一行,这里的protocol是一个Protocol的SPI类,调用protocol.export首先会调用protocol的包装类ProtocolFilterWrapper.export方法,由于这时的protocol类型是register类型,所以会调用RegisterProtocol.export方法。
Exporter<?> exporter = protocol.export(wrapperInvoker);
@Override
public Exporter export(Invoker invoker) throws RpcException {
if (REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}
在RegisterProtocol.export方法中,有一行启动通信服务器的代码,这里进去又会调用protocol.export方法,由于现在采用是dubbo协议,所以会调用DubboProtocol.export方法
final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);
DubboProtocol.export方法,可以看到这里会启动netty服务器
@Override
public Exporter export(Invoker invoker) throws RpcException {
URL url = invoker.getUrl();
//省略非核心代码..
// 开启NettyServer
openServer(url);
return exporter;
}
4.将服务信息注册到注册中心
启动完netty后,再回到RegisterProtocol.export方法,这里就是把接口注册到配置中心上面去
if (register) {
// 注册服务
register(registryUrl, registeredProviderUrl);
}
```
测试的时候采用的注册中心是zk,最后会调用ZookeeperRegistry.doRegister方法
```
public void register(URL registryUrl, URL registeredProviderUrl) {
Registry registry = registryFactory.getRegistry(registryUrl);
// 调用ZookeeperRegistry的register方法
registry.register(registeredProviderUrl);
}
```
ZookeeperRegistry.doRegister,可以看到这里会使用zk客户端向zk服务器创建节点,将服务信息注册到zk,最终在zk上面看到的节点就是有一个providers文件夹,下面有我们的demoService服务
```
@Override
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
5.监听路径registry.subscribe
在RegisterProtocol.export方法中,这里会监听zk节点,调用链为FailBackRegistry->ZookeeperRegistry.doSubscribe->notify通知所有的监听器OverrideListener,比如url有变化,会进行服务重新导出
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
难点
整体就是围绕protocol.export方法进行展开调用的,那么每次调用的时候dubbo又是怎么知道这个时候的protocol是registry还是dubbo类型的呢,由于采用的spi机制,会生成一个protocol的包装类,跟踪源码,个protocol的包装类代码如下
可以看export方法,在调用export方法的时候,每次会调用arg0.getUrl方法,这里的url就包含了协议的类型,url比如:registry://xxxxxxxxxxxxxxx,dubbo://xxxxxxxxxxxxxxxxxxxx
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
总结
核心就是protocol.export,需要理解SPI机制的调用。
到这一步就完成了从普通接口注册到配置中心,并且提供dubbo服务的流程。