Dubbo2.7源码分析-如何发布服务
Dubbo的服务发布逻辑是比较复杂的,我还是以Dubbo自带的示例讲解,这样更方便和容易理解。
Provider配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
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">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />
<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
</beans>
ApplicationContext
ClassPathXmlApplicationContext父类AbstractApplicationContext的方法refresh()在实例化bean之后的最后一步finishRefresh()中,此方法作用是发布相应的事件。
protected void finishRefresh() {
//省略LifeCycleProcessor刷新代码
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
// 省略注册到 LiveBeansView MBean代码
}
可以看到发布了一个ContextRefreshedEvent事件。
protected void publishEvent(Object event, ResolvableType eventType) {
//省略部分代码
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
//省略部分代码
首先获取ApplicationEvent事件广播对象,然后广播事件。
ApplicationEvent事件广播对象默认是SimpleApplicationEventMulticaster,这个对象是在AbstractApplicationContext的方法initApplicationEventMulticaster()初始化的,如果需要自定义,可以实现接口ApplicationEventMulticaster,并将bean的名字命名为applicationEventMulticaster。
接下来看看SimpleApplicationEventMulticaster类的multicastEvent方法。
@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
//事件类型
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//applicationListener
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
//异常执行
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
invokeListener(listener, event);
}
}
}
可以看到此方法会调用applicationListener的方法,对于Dubbo而言,就是ServiceBean.
怎么样获取到ServiceBean的呢?
ServiceBean实现了好几个接口,其中有两个接口ApplicationContextAware和ApplicationListener,其中ApplicationContextAware使ServiceBean具有获取ApplicationContext的能力(了解bean的生命周期),而ApplicationListener使ServiceBean具有响应事件响应的能力。dubbo实现ApplicationContextAware的目的是通过反射把自己添加到ApplicationContext的ApplicationListener列表中,即使不实现ApplicationContextAware接口,spring也会将实现了ApplicationListener接口的bean添加到其listener列表中的,dubbo这样做估计是向后兼容。
接着看invokeListener(listener, event);方法
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
//省略异常处理
}
}
invokeListener方法内部调用了doInvokeListener方法,而doInvokeListener方法调用了listener(ServiceBean)的onApplicationEvent方法.
ServiceBean
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
onApplicationEvent方法调用了export方法,export方法首先判断是否已经发布了服务,发布了则直接返回,没有发布则会判断是否需要延迟发布,如果需要延迟,则将发布服务做为一个任务添加到ScheduledThreadPoolExecutor线程池中,如果不延迟,则调用doExport方法立即发布服务。
doExport方法中会获取application/registries/monitor/module/protocols,并做一些检查和属性填充,然后调用doExportUrls();发布服务。doExportUrls()首先调用loadRegistries方法得到要注册的url,然后发布相关Protocol的服务。
简单叙述一下获取url的过程,url通过map组装参数和对应的值,参数有ApplicationConfig和RegistryConfig对象的属性以及path、dubbo、timestamp、pid、protocol、registry。
本示例applicationConfig是:
<dubbo:application name="demo-provider" qosPort="22222" id="demo-provider" />
registryURL
registryConfig是:
<dubbo:registry address="multicast://224.5.6.7:1234" id="org.apache.dubbo.config.RegistryConfig" />
最终map组装结果是:
最后得到registryURL是:
registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=4892&qos.port=22222®istry=multicast×tamp=1536112339884
然后调用doExportUrlsFor1Protocol方法发布服务,此方法开始部分是构造发布的服务URL,然后再发布url。
服务URL
URL包括以下几部分:服务端还是客户端标识,Dubbo版本,时间戳,Pid,服务的方法名,token、ApplicationConfig,MoudleConfig,ProviderConfig,ProtocolConfig,*MethodConfig对象的相关属性等。
例如本示例的url:
dubbo://192.168.124.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.124.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=8004&qos.port=22222&side=provider×tamp=1536114090787
我们来着重看一下在构造URL过程中port的获取过程。
//protocolConfig是配置的<dubbo:protocol />生成的对象
//name是protocol的name,本示例为"dubbo"
//map保存了url的键值对
Integer port = this.findConfigedPorts(protocolConfig, name, map);
findConfigedPorts顾名思义是查找配置的port,从哪查呢,先从系统环境变量查,如果没找到,再查找名字为name的protocol协义。
private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name, Map<String, String> map) {
Integer portToBind = null;
// 从环境变量从查找绑定的port
String port = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_BIND);
portToBind = parsePort(port);
// 如果没有从环境变量从查到,则从名称为name的protocol查找
if (portToBind == null) {
portToBind = protocolConfig.getPort();
if (provider != null && (portToBind == null || portToBind == 0)) {
portToBind = provider.getPort();
}
//这一句是关键,示例中name值是"dubbo",所以会实例化DubboProtocol,得到默认的port:20880
final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
if (portToBind == null || portToBind == 0) {
portToBind = defaultPort;
}
if (portToBind == null || portToBind <= 0) {
portToBind = getRandomPort(name);
if (portToBind == null || portToBind < 0) {
portToBind = getAvailablePort(defaultPort);
putRandomPort(name, portToBind);
}
logger.warn("Use random available port(" + portToBind + ") for protocol " + name);
}
}
//保存port到map中,以便后面url使用
map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind));
// 从环境变量中查找注册的port,如果没有找到,则等于绑定的Port.
String portToRegistryStr = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_REGISTRY);
Integer portToRegistry = parsePort(portToRegistryStr);
if (portToRegistry == null) {
portToRegistry = portToBind;
}
return portToRegistry;
}
有人或许有疑问,ServiceConfig在实例化时,不是已经加载过Protocol了吗?为什么还要使用ExtensionLoader加载一次呢?
final int defaultPort =ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
答: ServiceConfig实例化时,加载的Protocol是自适应的Protocol,是动态生成的,类名是Protocol$Adaptive(见Dubbo源码分析-SPI的应用中有分析)。而这里获取Port时加载的也是Protocol类,但指名了具体加载的是哪个Protocol(本示例是名称为dubbo的Protocol,即DubboProtocol,此类默认的端口是20880)。
发布URL
发布本地服务
调用ServiceConfig类的exportLocal(URL url)发布本地服务。
private void exportLocal(URL url) {
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
//本地服务url
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST)
.setPort(0);
ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
本示例的本地服务 url是:
injvm://127.0.0.1/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.124.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=3008&qos.port=22222&side=provider×tamp=1536125473655
重点看这一句:
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
其中涉及到ProxyFactory和Protocol,下面分别来看一看。
ProxyFactory
proxyFactory也是通过SPI加载的自适应类对象,类名为ProxyFactory$Adaptive,我们来看一下其class文件反编译后的源码。
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements ProxyFactory {
public ProxyFactory$Adaptive() {
}
public Invoker getInvoker(Object var1, Class var2, URL var3) throws RpcException {
if (var3 == null) {
throw new IllegalArgumentException("url == null");
} else {
String var5 = var3.getParameter("proxy", "javassist");
if (var5 == null) {
throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + var3.toString() + ") use keys([proxy])");
} else {
ProxyFactory var6 = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(var5);
return var6.getInvoker(var1, var2, var3);
}
}
}
public Object getProxy(Invoker var1, boolean var2) throws RpcException {
if (var1 == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
} else if (var1.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
} else {
URL var3 = var1.getUrl();
String var4 = var3.getParameter("proxy", "javassist");
if (var4 == null) {
throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + var3.toString() + ") use keys([proxy])");
} else {
ProxyFactory var5 = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(var4);
return var5.getProxy(var1, var2);
}
}
}
public Object getProxy(Invoker var1) throws RpcException {
if (var1 == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
} else if (var1.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
} else {
URL var2 = var1.getUrl();
String var3 = var2.getParameter("proxy", "javassist");
if (var3 == null) {
throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + var2.toString() + ") use keys([proxy])");
} else {
ProxyFactory var4 = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(var3);
return var4.getProxy(var1);
}
}
}
}
其中有三个方法,两个获取代理,一个获取Invoker。我们来看其中的getInvoker方法,默认获取名称为javassist的ProxyFactory。
由于本地服务URL中没有proxy参数,所以会调用JavassistProxyFactory的getInvoker(T proxy, Class type, URL url)方法,返回AbstractProxyInvoker的匿名类对象,此对象代理了服务对象(本示例中为DemoServiceImpl对象)。
其实(ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension("javassist");获取到的并不是JavassistProxyFactory对象,而是StubProxyFactoryWrapper对象,为什么呢?我们可以看下ExtensionLoader的getExtension(String name)方法
public T getExtension(String name) {
//检查name是否合法
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
//如果name等于true,则加载SPI的默认插件
if ("true".equals(name)) {
return getDefaultExtension();
}
//从当前插件类的缓存实例对象中获取
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//创建插件实例
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
} private T createExtension(String name) {
//从文件目录中加载插件类
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
} //从已加载的所有插件实例集合中获取
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//实例化插件实例,并放入集合
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
} //注入属性
injectExtension(instance); //插件的包裹类
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
重点的地方就在于插件的包裹类,StubProxyFactoryWrapper就是JavassistProxyFactory的包裹类,为什么这么说呢,因为StubProxyFactoryWrapper有一个带ProxyFactory参数的构造函数而且实现了ProxyFactory接口,具体可以看Extension的loadExtensionClasses方法源码(装饰者模式)。
Protocol
protocol对象也是一个自适应插件类,类名为Protocol$Adaptive,在上一篇文章中已有讲解。这个类会根据url的协义取得对应转义的插件类,没有的话,默认为dubbo协义,本地服务url协义为injvm,所以会加载InjvmProtocol,但是在加载InjvmProtocol并实例化后,发现InjvmProtocol还有对应的包裹类即(其实是所有Protocol的包裹类):ProtocolFilterWrapper和ProtocolListenerWrapper。ProtocolFilterWrapper类的作用是添加一些过滤器,ProtocolListenerWrapper的作用是添加ExporterListener。InjvmProtocol的export方法仅仅创建一个InjvmExporter实例,没有开启服务。
发布远程服务
如果注册url不为空,调用proxyFactory得到服务对象的代理类,然后使用protocol发布服务。由于注册url的协义是registry,在使用ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension("registry");会加载RegistryProtocol类并实例化,而且会添加其包裹类:ProtocolFilterWrapper和ProtocolListenerWrapper。而在这两个包裹类的export方法的首行,都会对registry协义进行单独处理。
RegistryProtocol
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
经过这两个包裹类后,最终会调用RegistryProtocol的export方法。
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//发布服务
//originInvoker中包含了代理服务对象的代理类
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//注册相关代码省略
//订阅相关代码省略
}
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
//key为发布的服务url
String key = getCacheKey(originInvoker);
//从map缓存中获取
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
//double check
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}
最重要的是这一句:
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
其中protocol也是Protocol$Adaptive对象,而invokerDelegete的URL是服务的url.
本示例中为:
dubbo://192.168.124.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.124.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=8468&qos.port=22222&side=provider×tamp=1536138127517
DubboProtocol
Protocol$Adaptive在解析URL的时得到dubbo,所以会加载DubboProtocol并实例化(DubboProtocol实际在前面获取默认接口时已经实例化并缓存起来了,此处取的是缓存的实例),并调用了DubboProtocol的export方法(与上面一样,在得到DubboProtocol实例后,仍然会在外面包裹一下)。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// 服务名:本例中为org.apache.dubbo.demo.DemoService:20880
String key = serviceKey(url);
//exporter 控制服务打开与关闭
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//省略发布子服务的相关代码
//打开服务
openServer(url);
//优化序列化处理
optimizeSerialization(url);
return exporter;
}
经过层层探索,曲折迂回,终于到openServer了,进去看看。
private void openServer(URL url) {
// 服务ip:端口号
String key = url.getAddress();
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
}
}
} else {
// 服务支持重置
server.reset(url);
}
}
}
可以看到其中有一个重要方法createServer(url)。
private ExchangeServer createServer(URL url) {
// 当服务关闭时,默认启动发送只读事件
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// 默认启动心跳
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
//str默认为netty
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
//添加编解码器
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
//启动服务,并传入请求处理器
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;
}
Exchangers
进入Exchangers.bind方法一探究竟。
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
//如果编码码器没有,则添加参数exchange
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
return getExchanger(url).bind(url, handler);
}
getExchanger(url)默认得到的是HeaderExchanger,可通过exchanger参数配置。
到HeaderExchanger中看看bind方法
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
Transporter
看下Transporters的bind方法。
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().bind(url, handler);
}
通过getTransporter方法获取一个自适应的Transporter,类名为Transporter$Adaptive,我们来看一下其源码:
package org.apache.dubbo.remoting;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements Transporter {
public Transporter$Adaptive() {
}
public Client connect(URL var1, ChannelHandler var2) throws RemotingException {
if (var1 == null) {
throw new IllegalArgumentException("url == null");
} else {
String var4 = var1.getParameter("client", var1.getParameter("transporter", "netty"));
if (var4 == null) {
throw new IllegalStateException("Fail to get extension(org.apache.dubbo.remoting.Transporter) name from url(" + var1.toString() + ") use keys([client, transporter])");
} else {
Transporter var5 = (Transporter)ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(var4);
return var5.connect(var1, var2);
}
}
}
public Server bind(URL var1, ChannelHandler var2) throws RemotingException {
if (var1 == null) {
throw new IllegalArgumentException("url == null");
} else {
String var4 = var1.getParameter("server", var1.getParameter("transporter", "netty"));
if (var4 == null) {
throw new IllegalStateException("Fail to get extension(org.apache.dubbo.remoting.Transporter) name from url(" + var1.toString() + ") use keys([server, transporter])");
} else {
Transporter var5 = (Transporter)ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(var4);
return var5.bind(var1, var2);
}
}
}
}
可以看到Transporter$Adaptive通过判断URL中是否有transporter参数,如果没有,就默认为netty。
示例中服务的URL为
dubbo://192.168.124.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.124.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=8004&qos.port=22222&side=provider×tamp=1536114090787
其中没有transporter参数,所以就使用netty。然后dubbo就去查找netty对应的是哪个Transporter,结果找到是NettyTransporter。
package org.apache.dubbo.remoting.transport.netty4;
//省略导入部分
public class NettyTransporter implements Transporter {
public static final String NAME = "netty";
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
@Override
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
return new NettyClient(url, listener);
}
}
NettyTransporter很简单,只有两个方法,一个用于开启服务,一个用于连接服务。到这里已经明白了Dubbo是如何发布一个服务的。
我们再进一步看下NettyServer的构造函数
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
可以看出其调用父类的构造函数,并传入url和handler的包裹类。handler的包裹类有哪些呢,进去看一看。
public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
.getAdaptiveExtension().dispatch(handler, url)));
}
注意到有一个接口Dispatcher,其自适应插件类是AllDispatcher,AllDispatcher的dispatch方法返回AllChannelHandler实例(此实例会将所有请求做为任务放入线程池中处理),在此实例基础上又包裹了HeartbeatHandler和MultiMessageHandler。
NettyServer会将MultiMessageHandler层层往上传到其父类AbstractPeer。
我们来回忆一下正向流程:
从ServiceConfig发布registryURL开始(见doExportUrlsFor1Protocol方法)
1.ServiceConfig生成服务实例的代理工厂类JavassistProxyFactory(ProxyFactory SPI默认代理工厂类)并包裹到DelegateProviderMetaDataInvoker(此类记录代理工厂类和服务信息ServiceBean(<dubbo:service />标签对应的类))
2.由于registryURL的protocol协义是registry,所以会加载RegistryProtocol(Protocol类的外面都包裹了ProtocolFilterWrapper和ProtocolListenerWrapper,下面不再特殊说明),并传入上一步的invoker。
3.RegistryProtocol又找到DubboProtocol,也会带上Invoker(此时的Invoker包含上一次的Invoker并带有服务地址(dubbo://IP:端口/服务接口全称?参数=xxx))。
所以requestHandler又会调用正向传过来的Invoker,经过ProtocolFilterWrapper和ProtocolListenerWrapper,最终调用到服务实现类相应的方法。
最后以一张图总结:
标识为SPI的类,是可以动态加载的。图片看不清楚的话,请查看原图。
再简单说下接收到请求后的处理流程:NettyServer接收到请求后,交给NettyServerHandler处理,NettyServerHandler转交给NettyServer的父类AbstractPeer处理,AbstractPeer又交给MultiMessageHandler处理,这样就开始了handler链的处理,handler的终点是HeaderExchangerHandler,HeaderExchangerHandler调用DubboProtocol传过来的成员变量requestHandler调用相应的服务类方法,然后得到结果,调用NettyServerHandler传过来的NettyChannel发送结果到Client。
Dubbo2.7源码分析-如何发布服务的更多相关文章
- angular源码分析:$compile服务——directive他妈
一.directive的注册 1.我们知道,我们可以通过类似下面的代码定义一个指令(directive). var myModule = angular.module(...); myModule.d ...
- redis源码分析之发布订阅(pub/sub)
redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...
- Dubbo2.7源码分析-Dubbo是如何整合spring-framework的
这篇文章是Dubbo源码分析的开端,与其说这篇文章是Dubbo源码分析,不如是spring源码分析,因为大部分都是在分析spring如何解析xml配置文件的,为了与后面的Dubbo源码分析保持一致,姑 ...
- 5.源码分析---SOFARPC调用服务
我们这一次来接着上一篇文章<4. 源码分析---SOFARPC服务端暴露>讲一下服务暴露之后被客户端调用之后服务端是怎么返回数据的. 示例我们还是和上篇文章一样使用一样的bolt协议来讲: ...
- 【图解源码】Zookeeper3.7源码分析,包含服务启动流程源码、网络通信源码、RequestProcessor处理请求源码
Zookeeper3.7源码剖析 能力目标 能基于Maven导入最新版Zookeeper源码 能说出Zookeeper单机启动流程 理解Zookeeper默认通信中4个线程的作用 掌握Zookeepe ...
- Fresco 源码分析(三) Fresco服务端处理(1) ImagePipeline为何物
4.3 服务端的处理 备注: 因为是分析,而不是设计,所以很多知识我们类似于插叙的方式叙述,就是用到了哪个知识点,我们再提及相关的知识点,如果分析到了最后,我想想是不是应该将这个架构按照设计的方式,重 ...
- dubbo源码分析02:服务引用
一.何时创建服务引用 引用官方文档的原话,如果将Dubbo托管在Spring-IOC容器下,Dubbo服务引用的时机有两个,第一个是在Spring容器调用ReferenceBean的afterProp ...
- Fresco 源码分析(三) Fresco服务端处理(2) Producer具体实现的内容
我们以mProducerFactory.newNetworkFetchProducer()为例,因为这些创建新的producer的方式类似,区别在于是否有包装的处理器,即如果当前处理器中没有正在处理的 ...
- motan源码分析一:服务发布及注册
motan是新浪微博开源的服务治理框架,具体介绍请看:http://tech.sina.com.cn/i/2016-05-10/doc-ifxryhhh1869879.shtml. 本系列的文章将分析 ...
随机推荐
- Send or receive files via Xshell
1. install lrzsz $ sudo apt-get install lrzsz 2. If you want to send file from your pc to pi, just d ...
- 再也不用线上倒数据了,使用 Faker 来造一批假的数据吧。
背景每当建表之后,常常需要写一批假的数据,用于测试算法.数据量的压力测试.列表翻页. 查看详情.数据关联等.这时就需要借助一款造数据的工具,它就是今天所要介绍的 Faker. 介绍 Faker 这个工 ...
- python跳坑---生成器
贵有恒,何必三更眠五更起,最无益,只怕一日曝十日寒. 好多东西要写下来一是方便自己,二可以分享给大家,我却一拖再拖. 工作的时候看别人代码中间结果,跳了个坑,关于python generator类型: ...
- PHP RBAC权限管理 基于角色的访问控制演示
RBAC rbac:Role Based Access Controll,基于角色的访问控制. 今天理一理RBAC,话不多说,直接切入主题 功能需求: 权限管理(无限极) 角色管理(可以分配权限) 管 ...
- C# 灵活用法拾遗
1.switch case : ; switch (a) { : Console.WriteLine("一"); break; : : Console.WriteLine(&quo ...
- mvc 读写txt文档
-----------------写入内容---------------- string userfile = "UserData.txt"; StreamWriter sw = ...
- linux 基本操作笔记
linux文件系统的实现 linux有一个树状结构来组织文件,数的顶端为根目录/,节点为目录,而末节点为所包含的数据文件.我们可以对文件进行多种操作,比如打开和读写. 存储设备分区 文 ...
- Docker容器的原理与实践(上)
本文来自网易云社区. 虚拟化 是一种资源管理技术,将计算机的各种资源予以抽象.转换后呈现出来, 打破实体结构间的不可切割的障碍,使用户可以比原本更好的方式来应用这些资源. Hypervisor 一种运 ...
- “全栈2019”Java多线程第二十三章:活锁(Livelock)详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- FunDA(10)- 用户功能函数模式:User Function Model
前面我们提过:FunDA就像一个管道(PipeLine).管道内流动着一串数据(Data)或者运算指令(Action).管道的源头就是能产生纯数据的数据源(Source),跟着在管道的中间会有一些节点 ...