关于dubbo-spi的那点事

it2022-05-05  188

关于SPI的一些概念

spi的英文名是 Service Provider Interface 中文名是服务提供接口 这个是java在语言层面提供一个可以扩展的途径,spi通过约定读取某个目录下的文件获取要加载的class类名,可以实现懒加载获取到我们所需要的服务并加载。

我们通过dubbo-spi来看一下spi是怎么样的一种约定和实现方式还有扩展。

dubbo-spi的几种注解类型

@SPI 类有spi注解才能实现spi扩展@Adaptive 对于已经有spi注解的类进行适配扩展有且只能有一个@Activate 对于URL类的参数进行一个参数,可以获取到特定group和key组合的ActivateList

在dubbo中所有的spi类的获取都是通过ExtensionLoader来获取对应spi类的扩展 通用的使用方法如下

//获取spi对应的扩展类,有Adaptive类的获取类型 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); System.out.println(compiler); //获取spi对应的扩展类,没有Adaptive类的获取,通过complile编译硬编码获取(通过URL扩展,比如通过URL的protocol参数类决定获取哪个实现类) Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); System.out.println(protocol); //通过URL来根据参数获取对应的Activate类列表 URL url = new URL("dubbo","ip",8080); List<ExporterListener> activateList = ExtensionLoader.getExtensionLoader(ExporterListener.class).getActivateExtension(url, Constants.EXPORTER_LISTENER_KEY); System.out.println(activateList); //根据spi配置文件里面配置的key来获取对应的类 Protocol jvmProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(InjvmProtocol.NAME); System.out.println(jvmProtocol); //获取默认的实现类(spi注解上的默认值作为key) Protocol defProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension(); System.out.println(defProtocol);

下面我们来看看ExtensionLoader内部是怎么实现spi服务加载的

从第一个使用方式的代码入手分析

119行之前就是做一些参数检验和判断传入的class参数是否有spi注解,然后有一个EXTENSION_LOADERS来缓存spi类.如果这个缓存类为空则new ****ExtensionLoader(type) 看下这个实例化做了什么

这里面最主要的是创建了一个objectFactory,这里是获取了一个objectFactory的适配类,实现跟最开始的那个分析的方法一样,那这里就直接跳过,这个最终返回的类是AdaptiveExtensionFactory

默认ExtensionFactory的实现类出了适配类例外还有SpiExtensionFactory(dubbo-spi方式去获取类)和SpringExtensionFactory(通过spring的上下文来获取类) ,这个适配类就是提供一个方法来遍历factorys通过对应的name来获取对应的实现类。

这个getExtensionLoader方法的方法到这里就结束了,然后在来看看getAdaptiveExtension

这里核心的就是createAdaptiveExtension,往下走

这里主要是通过反射获取已经实例化的calss类的set方法,然后获取方法名要set的对象名通过ExtensionLoader获取对应的对象set到实例化后的class类对象中,然后返回。

继续看

看728行,这行代码就是真正的spi类扩展的获取

看567行

590行之前就是通过spi上注解的值获取默认的cachedDefaultName,如果解析有多个会按照默认值依次取值,直到能根据value找到对应的spi类

591-593就是根据不同的目录加载不同目录下的文件,然后加载spi扩展类

文件目录的先后加载顺序是META-INF/dubbo/internal/->META-INF/dubbo/->META-INF/services/ 一般前面两个目录是在dubbo框架里面使用,第三个是交由用户来扩展。

继续看591行,代码比较长,这里就不全部贴了,简单描述下这个方法做了什么事情

获取Adaptive注解的类,并且缓存到cachedAdaptiveClass中

获取所有的装饰类,也就是实现这个接口类并且有构造器参数唯一并且是这个接口类的装饰类,缓存到cachedAdaptiveClass中

获取Activate注解的类 这个就是通过spi加载类的全过程,下图在截一个dubbo-internal的文件目录

其中dubbo协议的内容为

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol com.alibaba.dubbo.rpc.protocol.http.HttpProtocol com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol

回到getAdaptiveExtensionClass

736行就是根据class来获取适配类的硬编码,比如dubbo的Adaptive类就是

package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public com.alibaba.dubbo.rpc.Exporter export(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");com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1; String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } }

生成的适配类的主要作用是扩展通过url参数来获取对应的协议类调用协议类方法。

738就是获取不同的代码编译器,实现类有JavassistCompiler和JdkCompiler类 默认的就是JavassistCompiler,性能更高。

在看一个方法getExtension

核心是319行

主要是根据之前缓存的装饰类,循环装饰对象并且返回。 代码分析就到这里了,其他方法都大同小异,看懂了上面的代码,其他的自然不在话下。


最新回复(0)