Dubbo源码分析

​ 最近准备对Dubbo的历史漏洞进行分析,但觉得不懂Dubbo的设计和实现直接去分析漏洞比较困难,所以在分析漏洞前先分析Dubbo的源码及实现,值得一提的是Dubbo的官网也有非常详细的源码分析的过程。

SPI机制及实现

​ Dubbo的SPI是对JDK自身SPI的扩展实现,增加了IOC和AOP的功能,是Dubbo实现的核心,Dubbo SPI需要的配置文件放在/meta-inf/dubbo目录下,通过键值对的方式配置,如下所示:

1
2
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory

​ Dubbo的SPI和JDK自身的SPI对比如下,这也是Dubbo没有选择使用JDK自带SPI的原因。

image-20210910141629401

​ 可以通过@SPI注解将接口声明由Dubbo的SPI机制加载实现类。

image-20210910143121791

Dubbo如何实现SPI?

ExtensionLoader 是Dubbo SPI实现的核心类,每个定义的spi的接口都会构建一个ExtensionLoader实例。一般通过ExtensionLoader.getExtensionLoader获取ExtensionLoader实例。

getExtensionLoader首先判断是否为接口类型并且由@SPI注解修饰,也就是说只有@SPI修饰的接口才会由Dubbo的SPI机制去寻找实现类。下面会通过EXTENSION_LOADERS寻找是否已经有loader的实例,没有的话会创建一个并添加到EXTENSION_LOADERS中。

image-20210910142641943

​ 下面分析ExtensionLoader构造方法,如果type类型不为ExtensionFactory则先执行ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()

image-20210910144520134

getAdaptiveExtension首先从缓存中获取实例,没有则通过createAdaptiveExtension创建实例。

image-20210910144916264

createAdaptiveExtension首先通过 getAdaptiveExtensionClass().newInstance()创建实例,再通过injectExtension包装。

image-20210910145047509

getAdaptiveExtensionClass首先通过getExtensionClasses获取Class,找不到则通过createAdaptiveExtensionClass创建。

image-20210910145421185

getExtensionClasses首先通过缓存获取Class获取不到则通过loadExtensionClasses方法获取,获取后放到classesMap中。

image-20210910145535084

loadExtensionClasses首先获取SPI注解的属性值放到缓存中,下面通过loadDirectory从配置文件中加载Class,主要从META-INF/dubbo/META-INF/services/META-INF/dubbo/internal几个目录下加载文件。

image-20210910150018733

image-20210910150328371

​ 根据dir和type作为文件名加载资源,并通过loadResource加载类的信息并放到extensionClasses中。

image-20210910151027677 loadResource中读取文件并解析文件内容获取name接口实现类的名称,下面通过loadClass加载。

image-20210910151554367

​ 在loadClass中首先检查clazz是否是type的实现类,再去检测clazz的接口是否有Adaptive注解存在的话放到将类放到cachedAdaptiveClass缓存中,下面再通过是否有参数为clazz的构造方法,有的话将clazz存到cachedWrapperClasses中,下面查看实现类是否有Extension注解,有的话取出这个注解的值并赋值给name。下面获取name的值,可以通过xxx,xxx,xx=xxx.com等形式传入多个name,并通过saveInExtensionClassnameclass的值保存到extensionClasses中。image-20210910153042790

​ 下面在回到getAdaptiveExtensionClass方法中,首先在缓存中查找,找不到则会通过createAdaptiveExtensionClass创建Class。

image-20210910155850737

createAdaptiveExtensionClass首先根据typeSPI配置的value的值生成Adaptive包装类并编译为Class,也就是说我们获取的类型不是配置的实现类对象,而是Adaptive包装类对象。image-20210910160909268

​ 生成代码的逻辑比较复杂,我们就不深入分析了,不过我们可以拿到生成的代码,可以看看生成的代码主要做了什么。首先它是type接口的实现类,如果接口中的方法没有通过Adaptive修饰,则直接抛出异常。

image-20210910162020436

​ 对于Adaptive注解修饰的方法则会生成实现,首先检查Invoker的url是否为空,再获取协议信息,如果没有配置协议则默认使用dubbo协议,下面获取Protocol的实现类并执行实现类的export方法,其实也就是对export进行了一些包装,在执行export前加了一些验证逻辑。

image-20210910162254562

refer方法逻辑类似,只是最后调用实现类的refer方法。

image-20210910162528606

​ 下面我们再回到createAdaptiveExtension方法中,通过getAdaptiveExtensionClass()已经拿到了动态创建的Adaptive类并通过newInstance创建对象,下面通过injectExtension完成依赖注入。image-20210910163122043

如何实现IOC?

injectExtension获取setter方法,并通过objectFactory.getExtension(pt, property);获取需要注入的对象,通过反射调用setter方法完成依赖注入。

image-20210910163959267

objectFactory可能是下面三种实现类,也就是说除了可以通过Spi获取注入的对象也可以从spring中获取注入对象。而AdaptiveExtensionFactory则会循环调用多个factory获取对象。

image-20210910164637510

​ 一般objectFactory经过初始化后会封装为AdaptiveExtensionFactory并且包含了spispring两个工厂,也就是说默认会通过spispring两种方式加载需要注入的对象。

image-20210910165458497

为什么可以得到AdaptiveExtentionFactory?

​ 在容器启动时,会解析<dubbo:service对象,并创建一个serviceBean实例,这个实例是serviceConfig的子类,创建serviceBean实例的过程中也会执行父类的static属性,会执行如下操作。

image-20210910173512877

​ 跳过一些已经分析过的步骤,在执行ExtensionLoader构造方法时,会判断类型是否为ExtensionFactory类型,如果不是则会执行后面的代码。

image-20210910173558737

​ 进入getExtensionLoader方法,如果缓存中没有extensionLoader则重新创建一个,也就是说这里还会再调用一次ExtensionLoader的构造方法。

image-20210910174331550

​ 由于这次type的类型为ExtensionFactory,所以会返回一个ExtensionLoader对象但是此时objectFactory属性为空。

image-20210910174450700

​ 下面通过getAdaptiveExtesion获取ExtensionFactory的实现类,同样中间的过程不分析了,主要关注在loadExtensionClasses中获取了ExtensionFactory的实现类。

image-20210910191335022

​ 但是ExtensionFactory中明明配置了三个实现类,为什么加载后变成了两个而没有AdaptiveExtensionFactory

image-20210910191637278

​ 我们跟进资源加载部分的代码,可以看到确实读取到了AdaptiveExtensionFactory

image-20210910192020803

​ 在loadClass中,由于AdaptiveExtensionFactory实现类由Adaptive注解修饰,因此会该类到缓存cachedAdaptiveClass中并返回,并不会执行后面的saveInExtensionClass方法。

image-20210910192607693

​ 在执行完getExtensionClasses后,会判断cachedAdaptiveClass中是否有值,有的话则直接返回,所以这里其实通过getAdaptiveExtensionClass返回了AdaptiveExtensionFactory类。

image-20210910193304740

​ 所以下面是创建了AdaptiveExtensionFactory的实例。

image-20210910204329809

​ 而在AdaptiveExtensionFactory的构造方法中,通过 loader.getSupportedExtensions()获取扩展名,并通过loader.getExtension("spring")获取对应的工厂封装到factorties中。

image-20210913094202333

image-20210913094252060

​ 除了动态生成ProtocolAdaptive包装类外,还生成了proxyFactoryAdaptive包装类。

image-20210910170319633

​ 在Dubbo中主要使用了两种代理方式,即JDK和javassist。

image-20210910170540525

proxyFactoryAdaptive中主要实现了getProxygetInvoker方法,如果没有配置代理则默认使用javaassist动态代理。

image-20210910170647244

getInvoker中则根据传入的参数完成方法的调用。

image-20210910171257814

标签解析过程

​ 上面分析了Dubbo SPI机制的实现过程,下面分析下Dubbo 中配置的标签是如何解析的?

NamespaceHandler用来解析Spring遇到的所有这个特定的namespace配置文件中的所有elements,Dubbo 实现了DubboNamespaceHandler作为Dubbo标签中属性的处理器,在它的init方法中,配置了不同element的标签解析器。

image-20210913101339434

​ 并且Dubbo扩展了BeanDefinitionParser,自定义了DubboBeanDefinitionParser将标签中配置的属性值设置到Bean中,会对bean的属性赋值。

image-20210913113221590

image-20210913113753051

image-20210913113907181

image-20210913114042839

image-20210913114129549

image-20210913114350703

image-20210913114650696

image-20210913114438826

image-20210913114755892

服务导出过程

​ 通过DubboNamespaceHandler中的配置,可以知道service元素的配置信息会被方法ServiceBean中。

image-20210913115400466

​ 而ServiceBean中实现了ApplicationListener监听器接口,每当ApplicationContext发布ApplicationEvent时,实现ApplicationListener的Bean将自动被触发。

image-20210913115527858

​ 所以会触发ServiceBean.onApplicationEvent方法。

image-20210913120106343

​ 在ServiceBean.onApplicationEvent中通过export方法导出服务。

image-20210913120403548

​ 在ServiceBean#export中,调用父类ServiceConfig.export,首先判断做了一些检查,检测是否导出,和延时导出后通过doExport完成导出。

image-20210913120556258

doExport中首先判断是否已经导出过了,再判断是否设置path如果没有则将interfaceName作为path并调用doExportUrls方法。

image-20210913134215238

doExportUrls首先通过loadRegistries加载注册中心的地址,其次通过buildKey获取接口名,将接口名、实现类实例、接口Class封装到ProviderModel中。再通过initProviderModel将serviceName和providerModel 放到providedServices中。最后通过doExportUrlsFor1Protocol导出服务。

image-20210913135243969

image-20210913140236459

doExportUrlsFor1Protocol首先获取name属性值,为空则默认name为dubbo。创建一个存放参数的map,将一些配置的参数放到map中。

image-20210913141411753

​ 下面通过接口Class构造了接口的包装类,通过包装类获取所有的method,并将methed添加到map中。

image-20210913141940752

image-20210913142046319

​ 下面获取host和port,并通过这些信息和map中的信息构造一个org.apache.dubbo.common.URL对象,其中path为interfaceName,map中保存的信息被当作参数。

image-20210913142433464

image-20210913142928529

​ 当scope属性没有配置时,则默认先通过exportLocal先发布到本地,再发布到远程。

image-20210913143155375

本地导出

​ 本地导出主要在exportLocal中实现,首先判断协议名是否为injvm,如果是则表示已经导出过了,不再进行导出。下面构建本地导出的url,获取Invoker并导出。

image-20210913144237104

​ 这里的proxyFacory为之前分析SPI机制时动态生成的ProxyFactoryAdaptive类,它的getInvoker方法如下,默认情况下会使用javasist代理。

image-20210913144809592

JavassistProxyFactory#getInvoker首先创建了实现类的包装类,再创建了AbstractProxyInvoker对象并重写了doInvoke方法

image-20210913150629589

​ 而protocol也是在SPI机制动态生成的Adaptor,其export方法如下,

image-20210913151358964

​ 而具体调用哪个Protocol的export方法是由(org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName)的返回结果决定的,如果我们配置的是是injvm协议,则返回injvmProtocol的包装对象。

image-20210913155617492

​ 在ProtocolFilterWrapper#export中,首先判断是否是registry协议,如果是则直接导出否则通过buildInvokerChain构建过滤器链后再导出。

image-20210913155910479

buildInvokerChain构造过滤器链,只有当左右的Filter执行完后才会执行invoker的invoke方法。

image-20210913161909275

InjvmProtocol#export创建InjvmExporter对象完成本地导出。

image-20210913162133815

远程导出

​ 远程导出首先还是获取Invoker,再将Invoker和serviceBean封装到DelegateProviderMetaDataInvoker中,最后调用export方法导出。

image-20210913163041659

服务导出

​ 下面分析RegistryProtocol#export,在export方法中,主要通过RegistryProtocol#doLocalExport完成服务导出。首先从Invoker中获取key,其次创建了InvokerDelegate作为Invoker的委托类,最终通过protocol.export完成导出

image-20210913170935219

​ 由于我这里使用的是http协议,但HttpProtocol没有export方法,因此会调用父类AbstractProxyProtocol的export方法。在AbstractProxyProtocol#export中,首先判断是否已经导出过,如果没有则通过doExport完成导出。

image-20210913171719001

​ 在HttpProtocol#doExport中,首先互获取绑定地址,从serverMap缓存中获取server,没有的话通过bind创建server。

image-20210913173653552

​ bind的同时创建了InternalHandler,其中handle方法内容为当通过post请求时,会通过HttpInvokerServiceExporter.handleRequest处理请求。

image-20210913173903099

​ httpBinder也是Adaptive类,其内容如下,在export方法中,从url中获取server属性并根据server属性得到HttpBinder的实现类,并调用实现类的bind方法。如果没有配置server属性则默认为jetty。

1
2
3
4
5
6
7
8
9
10
public class HttpBinder$Adaptive implements org.apache.dubbo.remoting.http.HttpBinder {
public org.apache.dubbo.remoting.http.HttpServer bind(org.apache.dubbo.common.URL arg0, org.apache.dubbo.remoting.http.HttpHandler arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("server", "jetty");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.http.HttpBinder) name from url (" + url.toString() + ") use keys([server])");
org.apache.dubbo.remoting.http.HttpBinder extension = (org.apache.dubbo.remoting.http.HttpBinder)ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.http.HttpBinder.class).getExtension(extName);
return extension.bind(arg0, arg1);
}
}

​ 由于我们配置的server为tomcat因此会调用TomcatHttpBinder#bind方法,创建一个TomcatHttpServer.

image-20210913174901860

​ 下面构造Tomcat服务需要的一些参数,并且动态创建一个servlet,启动tomcat服务器。

image-20210913175327784

​ 启动server后,获取path,并将path作为key,通过createExporter创建的HttpInvokerServiceExporter作为值put到skeletonMap中。

image-20210913180242019

​ 给HttpInvokerServiceExporter添加接口信息和实现类.

image-20210914203023130

​ 最后创建一个Runnable对象并返回。

image-20210913180820871

​ 回到AbstractProxyProtocol#export中,得到runnable对象后,创建AbstractExporter对象并返回,将exporter放到缓存中后返回。

image-20210913180958894

服务注册

​ 首先获取registry实例,并且获取providerUrl,通过registerProvider将provider注册到providerInvokers中。

image-20210913183049914

image-20210913183319343

​ 下面调用register方法进行服务注册。

image-20210913202357260

​ register方法中主要通过doRegister完成注册。

image-20210913202655478

​ 由于我们使用的是zookeeper作为注册中心,所以会通过ZookeeperRegistry#doRegister完成服务注册。

image-20210913202725502

服务引用过程

​ 服务引用相当于生成了一个代理类,这个代理类可以给我们屏蔽远程调用的细节。

​ 服务引用分为三种,即本地引用,远程引用和注册中心引用。

​ 下面介绍来自字节面试:dubbo的服务引用过程

1
2
3
4
5
本地引入不知道大家是否还有印象,之前服务暴露的流程每个服务都会通过搞一个本地暴露,走 injvm 协议(当然你要是 scope = remote 就没本地引用了),因为存在一个服务端既是 Provider 又是 Consumer 的情况,然后有可能自己会调用自己的服务,因此就弄了一个本地引入,这样就避免了远程网络调用的开销。

直连远程引入服务,这个其实就是平日测试的情况下用用,不需要启动注册中心,由 Consumer 直接配置写死 Provider 的地址,然后直连即可。

注册中心引入远程服务,这个就是重点了,Consumer 通过注册中心得知 Provider 的相关信息,然后进行服务的引入

​ 服务引用主要通过ReferenceBean来实现,这个类实现了FactoryBean接口,在spring容器初始化时会调用ReferenceBean#getObject方法。

image-20210913205047434

get先调用checkAndUpdateSubConfigs检查属性值是否正确,再调用init完成服务引用。

image-20210913205302680

init方法首先将很多信息封装到map中,再调用createProxy创建代理。

image-20210913205600010

​ 判断是否为本地调用,如果为本地调用,则调用refprotocol.refer创建一个InjvmInvoker对象返回。

image-20210913205929446

​ 判断url是否为空,不为空则判断是远程引用还是注册中心引用。

image-20210913210629008

​ 获取注册中心的地址,判断是否配置监控中心,如果没有则跳过,最后向url中加入refer参数。

image-20210913211116652

​ 下面当url只有一个时则直接调用prtocol.refer生成invoker。如果有多个url则取最后一个registry的url作为registryURL,获取多个invoker添加到invokers中,并将invokers封装到StaticDirectory中,通过cluster封装为一个invoker,而这个invoker的地址为registryURL。

image-20210914092258934

​ 再看下refprotocol.refer是如何做的,由于我们使用的是registry协议,所以会调用RegistryProtocol#refer,首先获取url中的registry参数,并将参数的内容设置为url的协议名重新构造url,其次获取registry实例,如果要调用的接口名是RegistryService的实例,则直接构造Invoker返回,最后对group参数做处理,如果没有则直接调用doRefer完成核心的服务引用的逻辑。

image-20210914093935761

doRefer首先创建了RegistryDirectory对象,RegistryDirectory 是一种动态服务目录,实现了 NotifyListener 接口。当注册中心服务配置发生变化后,RegistryDirectory 可收到与当前服务相关的变化。接下来构造consumer的url并注册到注册中心,通过subscribe订阅provider和router等服务,订阅了之后 RegistryDirectory 会收到这几个节点下的信息,触发Invoker的生成。最后通过cluster封装directory得到Invoker,将Consumer的信息添加到ProviderConsumerRegTable后返回Invoker。

image-20210914101227398

​ 现在主要关注subscribe订阅方法,订阅过程中会调用对应协议的refer方法,由于我们配置的是http协议,但HttpProtocol没有实现refer方法,因此会调用父类AbstractProxyProtocol.refer

image-20210914102852897

AbstractProxyProtocol#refer首先调用HttpInvoker.doRefer获取http调用客户端代理类对象,并通过getInvoker将代理类转换为Invoker,创建AbstractInvoker对象并实现doInvoke方法,在doInvoke中调用invoker.invoke方法,完成服务调用并获取返回结果。

image-20210914111156433

HttpInvoker.doRefer创建了HttpInvokerProxyFactoryBean

​ 在spring中提供了HttpInvoker 通过HTTP协议实现RPC调用,Spring HttpInvoker使用Java序列化来序列化参数和返回值,然后基于HTTP协议传输经序列化后的对象。Spring HttpInvoker的服务端和客户端分别由HttpInvokerServiceExporterHttpInvokerProxyFactoryBean实现。

服务端处理如下:

image-20210914140905803

客户端处理如下:

image-20210914142047540

​ 创建HttpInvokerProxyFactoryBean对象后重写了createRemoteInvocation方法,根据不同的dubbo版本创建的不同的RemoteInvocation对象。

image-20210914114556775

​ 下面设置url和intercepte属性,并且创建了发送请求的客户端并封装到httpProxyFactoryBean中。创建SimpleHttpInvokerRequestExcutor对象并设置到httpProxyFactoryBean中。

image-20210914143424395

​ 下面调用afterPropertiesSet方法,创建ProxyFactory对象,通过getProxy获取AOP代理对象。

image-20210914143723921

​ 这里传入的interceptor是HttpInvokerProxyFactoryBean,这个Bean的父类HttpInvokerClientInterceptor继承了MethodInterceptor,因此这里相当于添加了一个环绕通知。

image-20210914192347578

​ 最后调用getObject实际上是返回HttpProxyFactoryBean的代理对象。

image-20210914143851670

服务调用过程

​ 服务调用是通过消费者的代理对象发起的,这个代理对象中包含了之前创建的服务引用。

image-20210914145531602

​ 查看org.apache.dubbo.rpc.proxy.InvokerInvocationHandler#invoke,封装调用的方法名和参数到RpcInvocation中,调用MockClusterInvoker.invoke

image-20210914160600831

org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke首先判断是否使用Mock机制,如果没有则调用AbstractClusterInvoker.invoke

image-20210914161157472

AbstractClusterInvoker.invoke得到Invoker,初始化负载均衡调用FailoverClusterInvoker.doInvoke

image-20210914164347557

FailoverClusterInvoker.doInvoke利用负载均衡策略选择一个invoker,并通过RpcContext记录调用过的Invoker,最后执行invoker的invoke方法。

image-20210914164911276

​ 回想一下服务引用的过程,真正执行请求的Invoker被封装为AbstractInvoker,所以会调用AbstractInvoker.invoke方法,设置invocation的invoker,并调用doInvoke方法。

image-20210914172239461

​ 我们实现的AbstractProxyProtocol重写了doInvoke方法,执行代理类的invoke方法。

image-20210914173300914

​ 这个代理类是AbstractProxyInvoker的实例,因此会调用AbstractProxyInvoker.invoke

image-20210914192727989

​ 在AbstractProxyInvoker.invoke调用了重写的doInvoke方法,也就是通过wapper.invokeMethod调用。也就是调用proxy的methodname方法。

image-20210914192838250

​ 由于我们添加了环绕通知,因此会调用HttpInvokerClientInterceptor.invoke执行调用请求。

image-20210914193337932

​ 在HttpInvokerClientInterceptor#executeRequest中获取executer并执行executeRequest方法。

image-20210914193634269

​ 由于我们之前服务引用在HttpInvokerProxyFactoryBean中设置的是SimpleHttpInvokerRequestExecutor,但SimpleHttpInvokerRequestExecutor中没有executeRequest,因此调用父类AbstractHttpInvokerRequestExecutor.executeRequest,在这个类中,将RemoteInvocation进行序列化后调用SimpleHttpInvokerRequestExecutor#doExecuteRequest完成请求发送。

image-20210914200306489

image-20210914200350526

​ 之前在服务导出时我们已经开启了tomcat服务并且创建了internalHandler设置到DispatcherServlet中,当接收到请求时,将通过internalHandler.handle处理请求。

image-20210914201638562

​ 获取HttpInvokerServiceExporter处理request请求。

image-20210914201705806

​ 通过readRemoteInvocation将传入的数据反序列化为RemoteInvocation对象,调用invokeAndCreateResult完成请求处理。

image-20210914201741834

​ 通过RemoteInvocationBasedExporter.invoke处理请求。

image-20210914201903323

​ 由于HttpInvokerServiceExporter没有invoke方法,因此会调用父类DefaultRemoteInvocationExecutor的invoke方法,调用RemoteInvocation.invoke

image-20210914203608528

​ 最后调用RemoteInvocation#invoke,通过反射执行方法。

image-20210914203828393

总结

​ 第一次对框架的源码进行分析,可能有一些地方没有分析清楚,不过了解到这种程度对于分析漏洞应该够用了。

参考资料