在上一篇文章Dubbo2.7.6启动原理之Provider中我已经探讨了dubbo服务启动的大致流程。可以看到,暴露服务逻辑在DubboBootstrap#exportServices()方法中。今天就来详细来探索一下暴露服务的实现细节。
如下为服务暴露过程中的涉及到的主要组件:
注意:
- 一个服务可以同时以多种协议暴露
- 一个服务可以同时注册在多个不同的注册中心
- 每一个服务接口都会转换为对应的ServiceConfig实例
如下是dubbo官方给出的服务暴露时序图:
如下就是服务接口暴露过程中的转换逻辑:
说明:
- 每一个接口根据一个协议暴露到一个注册中心时,最终会装换为Exporter实例!
上面整理了一下服务暴露的整体流程图,
下面就开始继续撸源码DubboBootstrap#exportServices()
//暴露所有的服务
private void exportServices() {
//从ConfigManager中获取所有的ServiceConfig实例,然后遍历,一个一个的暴露
configManager.getServices().forEach(sc -> {
// 服务export的核心逻辑方法是ServiceConfig#export()
ServiceConfig serviceConfig = (ServiceConfig) sc;
//将DubboBootstrap实例赋值给ServiceConfig
serviceConfig.setBootstrap(this);
//是否异步Export
if (exportAsync) {
//如果是异步,就获取一个线程池,来异步执行export逻辑
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
sc.export();
});
asyncExportingFutures.add(future);
} else {
//同步export
sc.export();
exportedServices.add(sc);
}
});
}
再来看ServiceConfig#export()
private DubboBootstrap bootstrap;
protected ServiceMetadata serviceMetadata;
public synchronized void export() {
//如果服务已经export就返回
if (!shouldExport()) {
return;
}
//如果DubboBootstrap为空,也就没有初始化,就初始化一下DubboBootstrap
if (bootstrap == null) {
//这里使用了单例模式,DubboBootstrap不会重复创建
bootstrap = DubboBootstrap.getInstance();
bootstrap.init();
}
checkAndUpdateSubConfigs();
//init serviceMetadata 初始化服务的原数据信息
serviceMetadata.setVersion(version);
serviceMetadata.setGroup(group);
serviceMetadata.setDefaultGroup(group);
serviceMetadata.setServiceType(getInterfaceClass());
//设置服务接口
serviceMetadata.setServiceInterfaceName(getInterface());
//设置服务的实现类
serviceMetadata.setTarget(getRef());
//export服务,判断是否延时export
if (shouldDelay()) {
//如果是延时export,就使用时间调度器延时执行服务export逻辑
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
//直接export服务
doExport();
}
//发布ServiceConfigExportedEvent事件
exported();
}
public void exported() {
// dispatch a ServiceConfigExportedEvent since 2.7.4
dispatch(new ServiceConfigExportedEvent(this));
}
可以看到,在最后使用事件模型,dubbo事件模型以后再研究,由于篇幅原因,这里不做介绍。
然后在看ServiceConfig#doExport()
/**
* The flag whether a service has unexported ,if the method unexported is invoked, the value is true
*/
private transient volatile boolean unexported;
/**
* The service name
*/
protected String path;
protected synchronized void doExport() {
//如果service已经被unexported,就抛出异常
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
//如果服务已经被暴露,就返回
if (exported) {
return;
}
exported = true;
//设置path值,只为接口的全限定名
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
//执行服务暴露逻辑
doExportUrls();
}
继续看ServiceConfig#doExportUrls() ,这里才是export核心逻辑的开始
/**
* The protocol list the service will export with
* Also see {@link #protocolIds}, only one of them will work.
*
* 一个服务ServiceConfig可能同时以多个协议export
*/
protected List<ProtocolConfig> protocols;
private void doExportUrls() {
//从ApplicationModel容器中获取服务仓库ServiceRepository(也是一个容器)
ServiceRepository repository = ApplicationModel.getServiceRepository();
//生产一个服务描述实例ServiceDescriptor
ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
//将服务提供者的信息存放到ServiceRepository中
repository.registerProvider(
getUniqueServiceName(),
ref,
serviceDescriptor,
this,
serviceMetadata
);
//获取服务注册中心的URL地址信息,一个服务可以同事在多个注册中心注册。所以此处是List
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
//遍历服务要暴露的协议集合。一个服务可能同时以dubbo协议,rest协议等export。一般默认是dubbo协议,也推荐是用dubbo协议
for (ProtocolConfig protocolConfig : protocols) {
//构建服务的path的key
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
// In case user specified path, register service one more time to map it to path.
repository.registerService(pathKey, interfaceClass);
// TODO, uncomment this line once service key is unified
serviceMetadata.setServiceKey(pathKey);
//真对一个协议,在一个或多个注册中心来export这个服务
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
可以看到,ServiceConfig#doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs)是真对一个协议,在一个或多个注册中心来export服务。
/**
* The exported services
*/
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
//获取协议名称,没有则默认使用dubbo
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
//组装参数
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
...
//init serviceMetadata attachments, ServiceMetadata中的attachments will be transferred to remote side
serviceMetadata.getAttachments().putAll(map);
// export service , 构建URL实例
String host = findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = findConfigedPorts(protocolConfig, name, map);
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
// You can customize Configurator to append extra parameters
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
//获取scope ,如果没有配置服务的scope时就不暴露服务
String scope = url.getParameter(SCOPE_KEY);
// don't export when none is configured
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
//如果scope != remote , 就以本地方式export
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
//如果scope不是local,就默认以remote方式export
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (CollectionUtils.isNotEmpty(registryURLs)) {
//遍历注册中心的地址。来一个一个的export
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
//如果协议为injvm, 就不需要暴露,在同一个jvm内的服务
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
//添加dynamic参数配置
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
//添加监控中心url配置, 可选
URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
...
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
// 使用ProxyFactory来生成接口的代理
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
//生成Invoker的包装类,实现了Invoker接口,其本身也是一个Invoker,只是在invoker的基础上扩展了ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//使用Protocol来暴露服务接口到对应的注册中心
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
//将这个Exporter实例存入ServiceConfig中的exporters属性中
exporters.add(exporter);
}
} else {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
* 处理服务的元数据信息
*/
WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
if (metadataService != null) {
metadataService.publishServiceDefinition(url);
}
}
}
this.urls.add(url);
}
至此大致的服务暴露流程就整理完毕。再来看实现细节。
- Protocol 协议
可以看到:这里的协议实现是通过ExtensionLoader.getAdaptiveExtension()去加载的
Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
那么这样得到的是一个什么样的结果呢?先看一下Protocol的相关实现结构图:
Protocol源码如下:
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
//provider端暴露接口
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
//consumer端引用接口
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
default List<ProtocolServer> getServers() {
return Collections.emptyList();
}
}
在@SPI中指定了默认实现的名称为dubbo ,那么就要去找SPI的配置文件啦,如下:
可以看到dubbo对应的实现类为DubboProtocol ,因此Protocol接口的默认实现类是DubboProtocol 。
那么上文中PROTOCOL就是DubboProtocol吗?
ExtensionLoader#getAdaptiveExtension();返回的实际上是dubbo通过字节码技术重新生成的一个类,生产的类的代码如下:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
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);
}
public java.util.List getServers() {
throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
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);
}
}
可以看到,重新生成的类,对于接口中标注有@Adaptive注解的方法才给出了实现。
而实现的方式也就是通过ExtensionLoader#getExtension(extName)来获取对于的类的实例。然后代其执行对于的逻辑。
下面再来看一下ExtensionLoader#getExtension(extName)的返回接口,这里extName=dubbo 。
private T createExtension(String name) {
//获取对于名称为dubbo的class ,这里值为org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//从缓存中加载class的实例,如果没有就新建一个
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//对类中的属性进行依赖注入
injectExtension(instance);
//处理包装类 , cachedWrapperClasses存放的是Protocol所有的Wrapper类,也就是上图中的三个
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
//循环wrapper类,将DubboProtocol一层一层包装起来
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance);
//然后再返回实例。这时候返回的就是包装了DubboProtocol的包装类的实例了
return instance;
} catch (Throwable t) {
...
}
}
由于Protocol接口存在三个Wrapper类,因此这里返回的是一个包装了DubboProtocol的包装类。如下图所示:
ExtensionLoader的相关源码解读在以前章节已经有详细讨论,这里就不在赘述。
服务暴露的所有细节都在 PROTOCOL.export(wrapperInvoker);中。篇幅原因这个下篇文章中再详细讨论
- ProxyFactory 代理工厂
ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
ProxyFactory也是一个SPI接口,并指定了默认实现是javassist对应的是JavassistProxyFactory 。然后这里在方法的上也都标注了@Adaptive ,并指定了参数PROXY_KEY ,也就是说我们可以指定PROXY_KEY的值来指定默认的实现.
源码如下:
/**
* ProxyFactory. (API/SPI, Singleton, ThreadSafe)
*/
@SPI("javassist")
public interface ProxyFactory {
@Adaptive({PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException;
@Adaptive({PROXY_KEY})
<T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;
@Adaptive({PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}
可以看到,它也有一个Wrapper类。结构的原理如Protocol 。最终PROXY_FACTORY的值为一个包装了JavassistProxyFactory的StubProxyFactoryWrapper类
PROXY_FACTORY值为:
到此dubbo中暴露服务的流程整理完毕!