文章目录
dubbo官方文档
官网源码服务导出分析: dubbo.apache.org/zh/docsv2.7…
dubbo官网分析讲的更细更好更清楚更完善,一定要先看看官网的分析
我这篇文章是自己过一遍,记录一下自己的理解.不喜勿喷!
解析dubbo标签
日常使用dubbo的时候通常是结合spring一起使用的,前面分析spring源码的时候大概讲了下spring解析xml文件:
在 spring 中定义了两个接口NamespaceHandler和BeanDefinitionParser,
spring主要通过BeanDefinitionParser来解析xml内容,
Spring 默认会加载 jar 包下的 META-INF/spring.handlers 文件寻找对应的 NamespaceHandler。 Dubbo-config 模块下的 dubboconfig-spring\src\main\resources\META-INF\spring.handlers。
DubboNamespaceHandler主要就是注册一堆BeanDefinitionParser来解析xml里面的各个标签:
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("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());
}
}
其中最重要的就是service解析为ServiceBean,reference解析为ReferenceBean,跟其他的config不同,实际ServiceBean,ReferenceBean都继承对应的config,ServiceConfig,ReferenceConfig.具体解析就不说了.
服务导出入口
服务导出主要都在ServiceBean类里:
ServiceBean继承了ServiceConfig,实现了InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener, BeanNameAware,
ApplicationEventPublisherAware
这些接口都是在spring的接口,具体作用以前讲spring的时候都说过,spring的Aware接口目的是为了让bean获取spring容器的服务,比如ApplicationContextAware就是获取ApplicationContext就是context容器对象,BeanNameAware就是获取类在spring中的名称等待.
其中比较重要的就是InitializingBean(类初始化的时候执行afterPropertiesSet方法)接口的方法,跟ApplicationListener(容器启动后会发一个事件通知 onApplicationEvent)接口的方法.
初始化工作,分析afterPropertiesSet方法
这个方法里面,就是把 dubbo 中配置的 application、registry、service、protocol 等信息,加载到对应的 config 实体中,便于后续的使用。
看看简单的一个服务提供provider的配置文件:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="demo-provider"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo"/>
<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>
<bean id="demoService2" class="org.apache.dubbo.demo.provider.DemoServiceImpl2"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" />
<dubbo:service interface="org.apache.dubbo.demo.DemoService2" ref="demoService2" />
</beans>
@Override
@SuppressWarnings({"unchecked", "deprecation"})
public void afterPropertiesSet() throws Exception {
if (getProvider() == null) {
Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
if (providerConfigMap != null && providerConfigMap.size() > 0) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if (CollectionUtils.isEmptyMap(protocolConfigMap)
&& providerConfigMap.size() > 1) { // backward compatibility
List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() != null && config.isDefault()) {
providerConfigs.add(config);
}
}
if (!providerConfigs.isEmpty()) {
setProviders(providerConfigs);
}
} else {
ProviderConfig providerConfig = null;
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (providerConfig != null) {
throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
}
providerConfig = config;
}
}
if (providerConfig != null) {
setProvider(providerConfig);
}
}
}
}
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
if (getModule() == null
&& (getProvider() == null || getProvider().getModule() == null)) {
Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
// 省略
服务导出时机容器刷新onApplicationEvent方法
这个方法是在spring容器刷新的时候执行.主要作用就是判断是否发布了服务,没发布则会执行发布:
// 监听spring上下文被刷新或者加载的时候
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 判断是否发布
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
//进行发布操作
export();
}
}
检查配置
ServiceBean中export方法是直接执行了父类的export方法,然后发布一个服务发布事件,我们主要看看ServiceConfig中的export方法;
@Override
public void export() {
super.export();
// 发布服务发布事件
publishExportEvent();
}
/**
* @since 2.6.5
*/
private void publishExportEvent() {
ServiceBeanExportedEvent exportEvent = new ServiceBeanExportedEvent(this);
applicationEventPublisher.publishEvent(exportEvent);
}
接下来走到 ServiceConfig的export方法
ServiceConfig不比其他Config 类一样去实现对配置文件中 service 的配置信息的存储,它主要是做一些服务发布的逻辑操作,对应的Service的一些配置信息都是放在了它的父类AbstractServiceConfig里面;
主要执行发布的逻辑是export->doExport->doExportUrls->doExportUrlsFor1Protocol
到doExportUrls就开始通过URL信息进行发布了,前面大都是一些配置检查;
public synchronized void export() {
// 检查或更新配置
checkAndUpdateSubConfigs();
// 判断当前服务是否发布,<dubbo:provider export="false"> false不发布
if (!shouldExport()) {
return;
}
// 是否延迟发布,<dubbo:provider delay="10"> delay延迟加载
if (shouldDelay()) {
delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
} else {
// 执行发布
doExport();
}
}
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
// 服务是否已经发布
if (exported) {
return;
}
// 设置发布状态
exported = true;
if (StringUtils.isEmpty(path)) {
// path服务路径,默认是接口名
path = interfaceName;
}
// 真正执行发布操作
doExportUrls();
}
多协议多注册中心导出服务
而对应的doExportUrlsFor1Protocol方法,发布指定协议的服务,服务发布主要有两个主要做的事情:
- 一个是导出服务到本地
- 一个是往注册中心上注册服务
- 一个是导出服务到本地
主要设计两个url,一个往注册中心上注册服务的url
registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=37225&qos.port=22222®istry=zookeeper×tamp=1644995987137
还有一个是dubbo服务暴露地址
前面的一大串 if else 代码,是为了把当前服务下所配置的参数进行解析,保存到 map 集合中
获得当前服务需要暴露的 ip 和端口
把解析到的所有数据,组装成一个 URL,大概应该是:
dubbo://172.18.172.31:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=172.18.172.31&bind.port=20880&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=12946&qos.port=22222®ister=true&release=&side=provider×tamp=1641364526791
源码部分解析:
private void doExportUrls() {
// 加载注册中心地址,组装成URL list
List<URL> registryURLs = loadRegistries(true);
// 可能配置多个协议,每个协议都需要向这些注册中心注册 <dubbo: protocol\>标签
for (ProtocolConfig protocolConfig : protocols) {
// 接口名,版本,组group , 组成的key
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
// 存储服务发布的元数据
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
// 发布对应协议的服务
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 部分省略...
// export service 发布服务
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
//组装url这里 生成服务发布的URL dubbo://172.18.172.31:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=172.18.172.31&bind.port=20880&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=12946&qos.port=22222®ister=true&release=&side=provider×tamp=1641364526791
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
// 通过ConfiguratorFactory 去实现动态改变配置的功能.
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
// 省略部分代码
后面会根据scope判断是否发布服务,发布到本地还是远程.
- scope不等于none发布服务
- scope不等于remote 则发布本地
- scope不等于local 则发布本地,
导出服务到本地
exportLocal 方法比较简单,首先根据 URL 协议头决定是否导出服务。若需导出,则创建一个新的 URL 并将协议头、主机名以及端口设置成新的值。然后创建 Invoker,并调用 InjvmProtocol 的 export 方法导出服务。 InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter,无其他逻辑。
源码:
// export to local if the config is not remote (export to remote only when config is remote) 本地注册
if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
private void exportLocal(URL url) {
// 如果 URL 的协议头等于 injvm,说明已经导出到本地了,无需再次导出
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URLBuilder.from(url)
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
// 创建 Invoker,并导出服务,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
InjvmProtocol:
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
对应生成的url例子:
injvm://127.0.0.1/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=127.0.0.1&bind.port=20880&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=1680&qos.port=22222®ister=true&release=&side=provider×tamp=1645088544993
导出服务到远程
Invoker 创建
封装成 invoker 其实就是想屏蔽调用的细节,统一暴露出一个可执行体,这样调用者简单的使用它,向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
- Invoker 是一个代理类,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。(后续单独分析)
- DelegateProviderMetaDataInvoker,因为 2.7 引入了元数据,所以这里对 invoker 做了委托,把 invoker 交给 DelegateProviderMetaDataInvoker 来处理
- 调用 protocol.export(invoker)来发布这个代理
- 添加到 exporters 集合
JavassistProxyFactory 创建了一个继承自 AbstractProxyInvoker 类的匿名对象,并覆写了抽象方法 doInvoke。覆写后的 doInvoke 逻辑比较简单,仅是将调用请求转发给了 Wrapper 类的 invokeMethod 方法。Wrapper 用于“包裹”目标类,Wrapper 是一个抽象类,仅可通过 getWrapper(Class) 方法创建子类。在创建 Wrapper 子类的过程中,子类代码生成逻辑会对 getWrapper 方法传入的 Class 对象进行解析,拿到诸如类方法,类成员变量等信息。以及生成 invokeMethod 方法代码和其他一些方法代码。代码生成完毕后,通过 Javassist 生成 Class 对象,最后再通过反射创建 Wrapper 实例。代码比较多,就暂时不分析了.源码里面有分析,感兴趣可以看看.我不怎么感兴趣.
protocol 的export方法
Protocol 的 export 方法是标注了 @ Adaptive 注解的,因此会生成代理类,然后代理类会根据 Invoker 里面的 URL 参数得知具体的协议,然后通过 Dubbo SPI 机制选择对应的实现类进行 export,而这个方法就会调用 对应Protocol的export 方法。我们这里是通过注册地址 //registryURL: registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=26632&qos.port=22222®istry=zookeeper×tamp=1644827868376
获取的invoker,这时候的protocol就是RegistryProcol.
导出服务到远程主要有服务导出与服务注册两个过程。这两个过程涉及到了大量的调用,比较复杂。
服务注册RegistryProcol
这个 RegistryProtocol 是用来实现服务注册的 这里面会有很多处理逻辑,主要如下:
- 调用 doLocalExport 发布服务
- 向注册中心注册服务
- 向注册中心进行订阅 override 数据
- 创建并返回 DestroyableExporter
对应的export方法
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 这里获得的是zookeeper注册中心的url,url: zookeeper://ip:port
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
// 服务提供者的url,dubbo://ip:port...
URL providerUrl = getProviderUrl(originInvoker);
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
// the same service. Because the subscribed is cached key with the name of the service, it causes the
// subscription information to cover.
// 订阅 override 数据。在 admin 控制台可以针对服务进行治理,比如修改权重,修改路由机制等,当注册中心有此服务的覆盖配置
// 注册进来时,推送消息给提供者,重新暴露服务
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
//export invoker
// 启动一个netty服务,执行dubboProtocol的export方法
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// url to registry
// // 把dubbo:// url注册到zk上
//根据 invoker 中的 url 获取 Registry 实例: zookeeperRegistry
final Registry registry = getRegistry(originInvoker);
// 获取要注册到注册中心的urll: dubbo://ip:port
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
//to judge if we need to delay publish
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
// 执行注册
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
// Deprecated! Subscribe to override rules in 2.6.x or before.
// 设置注册中心的订阅,监控节点变化
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time exportRandomAccess
// 返回新的exporter实例
return new DestroyableExporter<>(exporter);
}
发布服务DubboProtocol
在RegistryProcol里执行doLocalExport方法时,很自然的进到DubboProtocol里.
服务导出就是暴露服务,本质上应该是启动一个通信服务,主要的步骤是将本地 ip 的 20880 端口打开,进行监听。
InvokerDelegete: 是 RegistryProtocol 的一个静态内部类,该类是一个 originInvoker 的委托类,该类存储了 originInvoker,其 父类 InvokerWrapper 还会存储 providerUrl,InvokerWrapper 会调用 originInvoker 的 invoke 方法,也会销毁 invoker。可以 管理 invoker 的生命周期。
protocol.export(invokerDelegate) 注意这里又使用都了 Protocol 接口的自适应扩展点 也就意味着这个 Protocol 是一个动态代理类, Protocol$Adaptive 通过下面截图可以看到url中的参数。
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
//key: 从 originInvoker 中获得发布协议的 url: dubbo://ip:port/...
String key = getCacheKey(originInvoker);
//bounds: 一个 prviderUrl 服务 export 之后,缓存到 bounds 中,所以一个 providerUrl 只会对应一个 exporter
//bounds -chm ->computeIfAbsent if(map.get(key)==null){map.put()}
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
//orginInvoker-> InvokerDelegate(DelegateProviderMetaDataInvoker(invoker))
//对原有的 invoker,委托给了 InvokerDelegate
Invoker<?> invokerDelegete = new InvokerDelegate<>(originInvoker, providerUrl);
//将 invoker 转换为 exporter 并启动 netty 服务
//protocol.export -> DubboProtocol.export(本质上就是 暴露一个 20880的端口)
//protocol- >Protocol$Apaptive ->QosProtocolWrapper(ProtocolListenerWrapper(ProtocolFilterWrapper(DubboProtocol(invoker))))
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
});
}
这时候通过providerurl生成invoker,再往下执行的protocol就变成了dubboprotocol,
这里就走到了 DubboProtocol.export();
DubboProtocol就会通过netty创建个监听20880的本地服务:
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
//获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如
//${group}/copm.gupaoedu.practice.dubbo.ISayHelloService:${version}:20880
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
//将 <key, exporter> 键值对放入缓存中 这个是后继调用服务使用的
exporterMap.put(key, exporter);
//export an stub service for dispatching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
//开启服务暴露20880端口
openServer(url);
// 优化序列化
optimizeSerialization(url);
return exporter;
}
private void openServer(URL url) {
// find server. 获取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例
String key = url.getAddress();
//client can export a service which's only for server to invoke
//client 也可以暴露一个只有 server 可以调用的服务
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
//缓存, 一个key只对应一个exchangeServer 是否在 serverMap 中缓存了
ExchangeServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
}
}
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
// 创建服务,默认使用netty
private ExchangeServer createServer(URL url) {
//组装 url,在 url 中添加心跳时间、编解码参数
url = URLBuilder.from(url)
// send readonly event when server closes, it's enabled by default
.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
// enable heartbeat by default
.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT))
.addParameter(Constants.CODEC_KEY, DubboCodec.NAME)
.build();
//获得当前应该采用什么样的方式来发布服务, netty3, netty4, mina , grizzy,默认netty
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
///通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
}
ExchangeServer server;
try {
//这里会根据URL 调用对应的Server 默认netty 并且初始化Handler
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
总结
回过头再看看dubbo的核心架构图就明了许多了:
核心架构图:
整体设计图: