目录
六、DubboProtocol requestHandler
一、前言
Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现。
Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。
二、服务注册
因为Dubbo 是基于Spring的Schema进行扩展和加载,首先我们先看一下dubbo的配置文件内容。
这要还是应用了Spring的自定义标签功能,定义了dubbo标签,然后生命xsd的位置,我们进入xsd文件
dubbo的xsd文件在dubbo-config-spring项目的META-INF下面,注意还有这个spring.handlers,这个是用来解析文件标签的,进入到这个文件发现里面只有一个DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
进入到这个方法
代码很简洁,功能很清晰,首先我们看DubboNamespaceHandler继承了类NamespaceHandlerSupport,对于NamespaceHandlerSupport这个类,熟悉spring的话应该对他就不陌生了,NamespaceHandlerSupport
可以注册任意个BeanDefinitionParser
,而解析XML的工作委托给各个BeanDefinitionParser
负责。spring在扫描并加载BeanDefinition的时候会执行到这里,根据dubbo配置文件生成的BeanDefinition此刻交由spring管理。因为现在看的是服务端,所以这里我们主要看service对应的
ServiceBean
进入到ServiceBean
可以看到继承了ServiceConfig,实现了很多接口,而这些接口如果熟悉spring,肯定不陌生,都是Bean生命周期回调要实现的部分接口(InitializingBean, DisposableBean, ApplicationContextAware, BeanNameAware)或者是一些事件监听(ApplicationListener<ContextRefreshedEvent>),如果想了解Bean生命周期实现这些接口的执行时机,请移步。在这些实现的接口的重写的方法当中,大多是一些初始化操作,给属性赋值。
在afterPropertiesSet这个方法中例如给ProviderConfig赋值、注册中心(有可能是多注册中心)赋值、使用的协List<ProtocolConfig> protocols赋值、监控中心monitor赋值等。我们重点关注onApplicationEvent这个方法。
服务导出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。
在spring容器加载完成后触发contextrefreshedevent事件,这个事件会被实现了ApplicationListener接口的类监听到,执行对应的onApplicationEvent函数。这个方法里面核心只有这个export方法。进入export方法里面。
逻辑仍然很清晰,如果不暴露就直接返回,如果开启了延迟暴露就延迟发布接口,否则直接走doExport方法,注意这里的 isDelay 方法,这个方法字面意思是“是否延迟导出服务”,返回 true 表示延迟导出,false 表示不延迟导出。但是该方法真实意思却并非如此,当方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出。与字面意思恰恰相反,这个需要大家注意一下。这个方法的返回值让人有点困惑。该方法目前已被重构,详细请参考 dubbo #2686。进入到doExport方法。
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
//检查配置,配置填充属性,setter注入
checkDefault();
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {
//将类加载进jvm当中
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//检查校验
checkInterfaceAndMethods(interfaceClass, methods);
checkRef();
generic = Boolean.FALSE.toString();
}
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
//初始化application变量
checkApplication();
//初始化registries变量
checkRegistry();
//初始化protocols变量
checkProtocol();
//注入变量
appendProperties(this);
checkStubAndMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
//生成URL链接
doExportUrls();
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
//最终放进ApplicationModel里面
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
doExport方法内容稍多,但是功能并不是多复杂
- checkDefault:检查配置,配置provider填充属性,setter注入
- 然后是一些初始化赋值
- 初始化application变量 checkApplication();
- 初始化registries变量 checkRegistry();
- 初始化protocols变量 checkProtocol();
- 注入变量 appendProperties(this);
- 检查 local,stub是否存在,是否实现interfaceClass接口checkStubAndMock(interfaceClass);
而我们重点看的则是最后这个doExportUrls();进入到这个方法。Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持。
很明显整个逻辑分为两部分
①、loadRegistries 加载注册中心链接
loadRegistries首先第一步checkRegistry
对于checkRegistry方法,并不难理解
第一步配置文件写的注册中心不管是一个还是多个注册中心,在这里都会被缓存到registries中供后续处理,第二部,如果经历了第一步注册中心发现为空那么没办法就直接抛出异常,第三部是对于每一个注册中心配置对象进行属性填充。
继续回到刚才的loadRegistries,接下来的逻辑其实就是对于每一个注册中心都生成一个URL,放入List<URL> registryList当中,最终返回registryList。需要注意这个时候这个URL并不是最终通信的URL、这个URL只是注册到注册中心上的URL,并非dubbo通信使用的URL。
loadRegistries 方法总结主要包含如下的逻辑:
- 检测是否存在注册中心配置类,不存在则抛出异常
- 构建参数映射集合,也就是 map
- 构建注册中心链接列表
- 遍历链接列表,并根据条件决定是否将其添加到 registryList 中
关于多协议多注册中心导出服务就先分析到这,代码不是很多,接下来分析 URL 组装过程。
再回到刚才doExportUrls方法当中。
②、遍历protocols变量,注册到每个注册中心上去
配置检查完毕后,紧接着要做的事情是根据配置,以及其他一些信息组装 URL。前面说过,URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。URL 之于 Dubbo,犹如水之于鱼,非常重要。大家在阅读 Dubbo 服务导出相关源码的过程中,要注意 URL 内容的变化。
遍历 protocols,并在每个协议下导出服务,因为dubbo支持多种协议,默认使用的是dubbo协议,在不同服务上支持不同协议或者在同一个服务上使用多个协议,也就是将当前服务以某种协议在多个注册机上进行发布,进入到doExportUrlsFor1Protocol这个方法。
前面一大堆就不看了,都是一些准备工作,往map里面放值,
主要逻辑就是首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。需要注意的是,这里出现的 URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。
看下面这里。
看一下这里从url里面取值scope,如果scope为none,则表示不将服务暴露,直接就this.urls.add(url),进行返回了。如果进行服务暴露,这里将服务暴露分成两支:本地暴露和远程暴露。
上面代码根据 url 中的 scope 参数决定服务导出方式,分别如下:
- scope = none,不导出服务
- scope != remote,导出到本地
- scope != local,导出到远程
三、本地暴露
如果暴露不是远程的,则暴露到本地(仅当暴露是远程的时,暴露到远程),直接进入exportLocal这个方法当中。
如果url.getProtocol()不等于injvm才去执行if里面的内容,如果url.getProtocol()等于injvm的话其实就是说已经向本地暴露了,不需要重复操作。然后接下来分为三步
第一步是封装local URL
第二步是进行服务暴露
我们debug走进getInvoker方法,发现这是利用了dubbo SPI机制生成的动态实现类,但是这种方式对于调试程序极为不便,所以我们生成动态实现类的文件,如果不知道怎么生成动态实现类文件,请移步。
生成的动态实现类如下:
/**
* @author: wenyixicodedog
* @create: 2020-07-03
* @description:
*/
public class ProxyFactory$Adaptive implements ProxyFactory {
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, URL arg2) throws RpcException {
if (arg2 == null) throw new IllegalArgumentException("url == null");
URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if (extName == null)
throw new IllegalStateException("Fail to get extension(ProxyFactory) name from url(" + url.toString() + ") use keys" + "([proxy])");
ProxyFactory extension = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
URL url = arg0.getUrl();
String extName = url.getParameter("proxy", "javassist");
if (extName == null)
throw new IllegalStateException("Fail to get extension(ProxyFactory) name from url(" + url.toString() + ") use keys" + "([proxy])");
ProxyFactory extension = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
}
debug继续往下走,从url中根据proxy为key进行取值,其实这个时候取出来的是为空的,所以就用了默认的javassist,然后利用SPI机制动态获取ProxyFactory类型的javassistProxyFactory
继续往getInvoker里面走,直接调用了proxyFactory.getInvoker
这个方法首先看返回值是个Invoker类型的,然后利用内部类进行实现,创建匿名invoker对象,并且重写doInvoke抽象方法,invoker其实就是对proxy, type, url三者信息的封装,至于Wrapper.getWrapper其实是利用字节码生成的代理。
在 Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明,这里引用一下。
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
获取到Invoker之后我们返回到刚才的exportLocal方法
export将服务进行暴露出去,进入到这个方法。
最终赋值InjvmExporter的Invoker、key属性,将key、InjvmExporter放进exporterMap中缓存起来以供使用。
第三步是将exporter放进exporters
export暴露服务之后我们返回到刚才的exportLocal方法
最终直接将exporter放进exporters缓存进服务列表当中。
四、远程暴露
接下来看如何将服务远程注册,回到doExportUrlsFor1Protocol这个方法的远程暴露处
其实乍一看和本地暴露大同小异,首先获取Invoker对象,然后将Invoker对象export出去,最终缓存进exporters列表当中
先不着急,一步一步来看。
getInvoker和本地类似,除了url地址我是127.0.0.1了,一直往下走
整个过程都很简单,注释已经写的很清楚,然后一步一步往回走
将获取到的Invoker进行一层封装,获取到wrapperInvoker,然后进入到下面的export方法,进入到Protocol$Adaptive这个动态生成的类,关于这个类如何生成,上面有介绍,进入到这个 类的export方法
因为invoker.getUrl().getProtocol()这里始终是registry,所以一直往下走,都类似
在qos这个类中可以执行有关qos服务的逻辑
private void startQosServer(URL url) {
if (!hasStarted.compareAndSet(false, true)) {
return;
}
try {
boolean qosEnable = Boolean.parseBoolean(url.getParameter(QOS_ENABLE,"true"));
if (!qosEnable) {
return;
}
int port = Integer.parseInt(url.getParameter(QOS_PORT,"22222"));
boolean acceptForeignIp = Boolean.parseBoolean(url.getParameter(ACCEPT_FOREIGN_IP,"true"));
Server server = com.alibaba.dubbo.qos.server.Server.getInstance();
server.setPort(port);
server.setAcceptForeignIp(acceptForeignIp);
server.start();
} catch (Throwable throwable) {
//throw new RpcException("fail to start qos server", throwable);
}
}
继续往下走,来到这个export方法
可以明显的看到这个方法主要做了三件事
- 暴露Invoker,获取exporter,内部开启netty服务
- 发送注册请求到zk服务器端
- 发送订阅请求到zk服务器端
①、暴露Invoker,获取exporter,开启netty服务
进入doLocalExport一直往下走,执行protocol.export方法,一路export方法
最终来到这个export方法,将exporter缓存进exporterMap中,开启netty服务,然后返回exporter。netty服务相关的等会我们再看。
②、发送注册请求到zk服务器端
服务注册操作对于 Dubbo 来说不是必需的,通过服务直连的方式就可以绕过注册中心。但通常我们不会这么做,直连方式不利于服务治理,仅推荐在测试服务时使用。对于 Dubbo 来说,注册中心虽不是必需,但却是必要的。因此,关于注册中心以及服务注册相关逻辑,我们也需要搞懂。
回到RegistryProtocol的export方法,register这个方法就是往zk注册服务
最终来到FailbackRegistry的register方法,很明显两个主要的功能:发送注册请求到zk服务器端和将失败的注册请求记录到失败列表中,并定期重试
如上,我们重点关注 doRegister 方法调用即可,其他的代码先忽略。doRegister 方法是一个模板方法,因此我们到 FailbackRegistry 子类 ZookeeperRegistry 中进行分析。如下:
protected void doRegister(URL url) {
try {
// 通过 Zookeeper 客户端创建节点,节点路径由 toUrlPath 方法生成,路径格式如下:
// /${group}/${serviceInterface}/providers/${url}
// 比如
// /dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1......
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register...");
}
}
如上,ZookeeperRegistry 在 doRegister 中调用了 Zookeeper 客户端创建服务节点。节点路径由 toUrlPath 方法生成,该方法逻辑不难理解,就不分析了。接下来分析 create 方法,如下:
public void create(String path, boolean ephemeral) {
if (!ephemeral) {
// 如果要创建的节点类型非临时节点,那么这里要检测节点是否存在
if (checkExists(path)) {
return;
}
}
int i = path.lastIndexOf('/');
if (i > 0) {
// 递归创建上一级路径
create(path.substring(0, i), false);
}
// 根据 ephemeral 的值创建临时或持久节点
if (ephemeral) {
createEphemeral(path);
} else {
createPersistent(path);
}
}
上面方法先是通过递归创建当前节点的上一级路径,然后再根据 ephemeral 的值决定创建临时还是持久节点。createEphemeral 和 createPersistent 这两个方法都比较简单,这里简单分析其中的一个。如下:
public void createEphemeral(String path) {
try {
// 通过 Curator 框架创建节点
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
好了,到此关于服务注册的过程就分析完了。整个过程可简单总结为:先创建注册中心实例,之后再通过注册中心实例注册服务。
③、发送订阅请求到zk服务器端
再看RegistryProtocol的export方法,subscribe这个方法就是往zk订阅服务
同样两个主要的功能:发送订阅请求到zk服务器端和将失败的注册请求记录到失败列表中,并定期重试,详细内容就不分析了。
至此远程暴露大概就完成了。
回到最初的ServiceConfig的doExportUrlsFor1Protocol方法,同样将exporter缓存进exporters中。
经过doExportUrls方法的for循环就将当前这个服务以指定的额某种协议在多个注册机上进行发布完成了。
五、开启netty(openServer(url))
刚才说到开启netty服务的方法openServer(url),直接打开这个方法
从url中以isserver为key进行取值,默认为true,得到布尔型变量isServer,如果为true则先从serverMap中取ExchangeServer,如果server已经存在,则重置,
在同一台机器上(单网卡),同一个端口上仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时则调用 reset 方法重置服务器的一些配置。考虑到篇幅问题,关于服务器实例重置的代码就不分析了。
如果不存在,则新建并且放入serverMap中缓存起来。直接进入到createServer方法中
在bind方法中首先获取到Exchanger,然后进行bind操作,Exchanger这一层是接收用户请求并且做出相应的一层转换层
继续往下走会发现如果我们url中没有指定哪种Exchanger,就是用默认的header
所以我们从上面bind方法直接进入HeaderExchanger的bind方法(其实Exchanger也就只有HeaderExchanger这一个实现类)。
继续进入到bind方法,然后调用getTransporter().bind的方法,接下来就是获取nettyServer。
点击查看nettyServer的构造创建。前面都是一些基础属性的赋值部分、然后我们看到调用了doOpen方法,它重写了抽象类父类的AbstractServer中的doOpen抽象方法。
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
//启动netty服务
bootstrap = new ServerBootstrap(channelFactory);
//设置请求处理器
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();
// https://issues.jboss.org/browse/NETTY-365
// https://issues.jboss.org/browse/NETTY-379
// final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setPipelineFactory(() -> {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
/*int idleTimeout = getIdleTimeout();
if (idleTimeout > 10000) {
pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
}*/
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
//netty接受客户端请求的处理器
pipeline.addLast("handler", nettyHandler);
return pipeline;
});
// bind
channel = bootstrap.bind(getBindAddress());
}
doOpen方法启动netty服务、创建请求处理器,整个doOpen方法执行完毕,netty服务就已经启动成功啦。
六、DubboProtocol requestHandler
nettyServer一旦创建成功,使用的是nettyHandler的messageReceived进行请求的处理、而在这个方法里面我们看到使用的是handler.receive接收请求
首先我们需要明白这个handler是什么东西,然后才能明白后面他的处理流程。这个handler其实在之前createServer的过程中我们会发现一路传进来一个handler(其中会进行包装,赋父类值等过程),这个handler在DubboProtocol中以匿名内部类的方式被创建,然后重写了很多方法,其中我们看receive和reply方法,其实这里才是真正执行业务的逻辑。
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
//根据客户端请求信息封装key值从exporterMap取出接口实现类对象invoker
Invoker<?> invoker = getInvoker(channel, inv);
// need to consider backward-compatibility if it's a callback
if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
String methodsStr = invoker.getUrl().getParameters().get("methods");
boolean hasMethod = false;
if (methodsStr == null || methodsStr.indexOf(",") == -1) {
hasMethod = inv.getMethodName().equals(methodsStr);
} else {
//循环验证客户端请求的方法,服务端是否存在对应的实现
String[] methods = methodsStr.split(",");
for (String method : methods) {
if (inv.getMethodName().equals(method)) {
hasMethod = true;
break;
}
}
}
//不存在直接抛出异常
if (!hasMethod) {
logger.warn(new IllegalStateException("The methodName " + inv.getMethodName() + " not found in callback service interface ,invoke will be ignored. please update the api interface. url is:" + invoker.getUrl()) + " ,invocation is :" + inv);
return null;
}
}
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
//利用反射调用实现类对象的方法相应请求
return invoker.invoke(inv);
}
throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}
/**
* @Author: wenyixicodedog
* @Date: 2020-07-03
* @Param: [channel, message]
* @return: void
* @Description: 接受客户端请求并返回响应消息
*/
@Override
public void received(Channel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
//服务端处理并返回响应消息
reply((ExchangeChannel) channel, message);
} else {
super.received(channel, message);
}
}
然后我们进入刚才的handler.received(channel, e.getMessage())方法里面。其实就是进入到了这个内部类的receive方法,显而易见能够看到直接调用的就是reply方法,然后reply方法里面我们只看核心逻辑
- getInvoker(channel, inv) 获取到Invoker
- invoker.invoke(inv) 由内部类处理请求
①、获取到Invoker,这个Invoker就是之前获取到的AbstractProxyInvoker的内部类
②、invoker.invoke调用AbstractProxyInvoker抽象方法然后由具体实现(jdk/javassist)里面的内部类处理请求,处理结果封装在了RPCResult里面。
意思就是getInvoker里面的匿名内部类的doInvoke方法,在这个方法里面,直接执行wrapper.invokeMethod调用接口的我们自己实现的方法执行业务逻辑。最终RPCResult进行序列化返回client。client只需要接收数据然后反序列化就取出数据了。
个人才疏学浅,信手涂鸦,dubbo框架更多模块解读相关源码持续更新中,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......
公众号:wenyixicodedog