Dubbo 源码分析

语言: CN / TW / HK

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

SPI机制及实现

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

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

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

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

Dubbo如何实现SPI?

ExtensionLoader 是Dubbo

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

如何实现IOC?

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

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

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

为什么可以得到AdaptiveExtentionFactory?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

标签解析过程

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

NamespaceHandler 用来解析Spring遇到的所有这个特定的namespace配置文件中的所有elements,Dubbo

实现了 DubboNamespaceHandler 作为Dubbo标签中属性的处理器,在它的init方法中,配置了不同element的标签解析器。

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

服务导出过程

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

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

所以会触发 ServiceBean.onApplicationEvent 方法。

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

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

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

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

放到 providedServices 中。最后通过 doExportUrlsFor1Protocol 导出服务。

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

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

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

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

本地导出

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

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

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

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

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

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

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

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

远程导出

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

服务导出

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

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

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

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

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

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 .

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

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

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

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

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

服务注册

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

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

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

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

服务引用过程

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

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

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

本地引入不知道大家是否还有印象,之前服务暴露的流程每个服务都会通过搞一个本地暴露,走 injvm 协议(当然你要是 scope = remote 就没本地引用了),因为存在一个服务端既是 Provider 又是 Consumer 的情况,然后有可能自己会调用自己的服务,因此就弄了一个本地引入,这样就避免了远程网络调用的开销。
直连远程引入服务,这个其实就是平日测试的情况下用用,不需要启动注册中心,由 Consumer 直接配置写死 Provider 的地址,然后直连即可。
注册中心引入远程服务,这个就是重点了,Consumer 通过注册中心得知 Provider 的相关信息,然后进行服务的引入

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

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

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

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

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

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

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

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

doRefer 首先创建了 RegistryDirectory 对象,RegistryDirectory 是一种动态服务目录,实现了

NotifyListener 接口。当注册中心服务配置发生变化后,RegistryDirectory

可收到与当前服务相关的变化。接下来构造consumer的url并注册到注册中心,通过 subscribe 订阅provider和router等服务,订阅了之后

RegistryDirectory

会收到这几个节点下的信息,触发Invoker的生成。最后通过cluster封装directory得到Invoker,将Consumer的信息添加到 ProviderConsumerRegTable 后返回Invoker。

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

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

HttpInvoker.doRefer 创建了 HttpInvokerProxyFactoryBean

在spring中提供了HttpInvoker 通过HTTP协议实现RPC调用,Spring

HttpInvoker使用Java序列化来序列化参数和返回值,然后基于HTTP协议传输经序列化后的对象。 Spring HttpInvoker 的服务端和客户端分别由 HttpInvokerServiceExporterHttpInvokerProxyFactoryBean 实现。

服务端处理如下:

客户端处理如下:

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

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

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

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

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

服务调用过程

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

查看 org.apache.dubbo.rpc.proxy.InvokerInvocationHandler#invoke ,封装调用的方法名和参数到 RpcInvocation 中,调用 MockClusterInvoker.invoke
org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke 首先判断是否使用Mock机制,如果没有则调用 AbstractClusterInvoker.invoke
AbstractClusterInvoker.invoke 得到Invoker,初始化负载均衡调用 FailoverClusterInvoker.doInvoke

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

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

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

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

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

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

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

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

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

获取 HttpInvokerServiceExporter 处理request请求。

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

通过 RemoteInvocationBasedExporter.invoke 处理请求。

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

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

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

参考资料

  • https://dubbo.apache.org/zh/docs/

  • https://blog.csdn.net/qq_35190492/article/details/108461885