Gemini技术窝 发表于 2021-9-27 00:01:42

Dubbo源码深度分析:SPI机制实现

摘要:本文分析PROTOCOL的具体实现类是什么,SPI是怎样选择默认实现类,怎样通过SPI进行动态编译后,天生具体的实现类DubboProtocol。


Dubbo的Provider其实就是将本地接口作为导出服务,暴露给其他服务使用。
导出服务分为本地服务和长途服务,长途服务分为先注册中心、直连方式。代码如下:
// Protocol层SPIprivate static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); /*** ProxyFactory的SPI* A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its* default implementation*/private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); // 天生动态署理对象Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // 真正导出服务Exporter exporter = PROTOCOL.export(wrapperInvoker);exporters.add(exporter);

这段重点的代码基本都用到了Dubbo的一个特性:SPI,以是我们先跳出这个坑,去简单相识一下,SPI是什么东东。
SPI机制

Dubbo 为每个功能点提供了一个 SPI 扩展接口, Dubbo 框架在使用扩展点功能的时候是对接口进行依赖的,而一个扩展接口对应了 一系列的扩展实现类。那么怎样选择使用哪一个扩展接口的实现类呢?其实这是使用适配器模式来做的。
首先我们看看什么是适配器模式,比如 Dubbo 提供的扩展接口 Protocol,其定义如下:
/*** Protocol. (API/SPI, Singleton, ThreadSafe)*/@SPI("dubbo")public interface Protocol {    @Adaptive   Exporter export(Invoker invoker) throws RpcException;}Dubbo 会使用动态编译技能为接口Protocol天生一个适配器类 Protocol$Adaptive的对象实例,在Dubbo框架中 必要使用Protocol的实例时,实际上就是使用Protocol$Adaptive的对象实例来获取具体的 SPI 实现类的。
在 Dubbo 框架中, protocol 的 定义为 :
private static final Protocol protocol =ExtensionLoader.getExtensionLoader(ProtocoI.class).getAdaptiveExtension()总结一下就是,适配器类 Protocol$Adaptive 会根据传递的协议参数的不同,力日载不同的 Protoco l 的 SPI 实现。
其实在 Dubbo 框架中 , 框架会给每个 SPI 扩展接口动态天生一个对应的适配器类,并根据参数来使用增强 SPI 以选择不同的 SPI 实现 。
动态编译又是什么

众所周知, Java 程序要想运行首先必要使用 javac 把源代码编译为 class 宇节码文件 ,然后使用 JVM 把 class 字节码文件加载到内存创建 Class 对象后,使用 Class 对象创建对象实例。正常情况下我们是把所有源文件静态编译为字节码文件, 然后由 JVM 同一加载,而动态编译则是在 JVM 进程运行时把源文件编译为字节码文件,然后使用字节码文件创建对象实例。
然后简单来说,就是dubbo内部使用了JavassistCompiler,将指定的源代码文件编译成了Class对象,有了 Class 对象后 ,我们就可以使用 newlnstance()方法创建对象实例了。
末了一问:SPI怎么做的

说了这么多,我们回来看看ExtensionLoader的源码:
public staticExtensionLoader getExtensionLoader(Class type) {    if (type == null) {      throw new IllegalArgumentException("Extension type == null");    }    //......   // 第一次访问某个扩展接口时必要新建一个对应的ExtensionLoader 并放入缓存,背面就直接从缓存中获取 。    ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);    if (loader == null) {      EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));      loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);    }    return loader;}这里只是加入缓存,没什么特别。
public T getAdaptiveExtension() {    Object instance = cachedAdaptiveInstance.get();    if (instance == null) {      if (createAdaptiveInstanceError != null) {            throw new IllegalStateException("Failed to create adaptive instance: " +                  createAdaptiveInstanceError.toString(),                  createAdaptiveInstanceError);      }         // 通过双重锁检查创建 cachedAdaptivelnstance 对象,接口对应的适配器对象就生存到这个对象里。      synchronized (cachedAdaptiveInstance) {            instance = cachedAdaptiveInstance.get();            if (instance == null) {                try {                  instance = createAdaptiveExtension();                  cachedAdaptiveInstance.set(instance);                } catch (Throwable t) {                           // ......                }            }      }    }   return (T) instance;} private T createAdaptiveExtension() {    try {      // 获取适配器对象的一个实例,然后进行依赖注入,      // getAdaptiveExtensionClass() 实际是调用了编译组件进行动态编译类文件      return injectExtension((T) getAdaptiveExtensionClass().newInstance());    } catch (Exception e) {      throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);    }} //....../*** 使用编译组件进行类文件的动态编译*/private Class createAdaptiveExtensionClass() {    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();    ClassLoader classLoader = findClassLoader();    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();    return compiler.compile(code, classLoader);}上面的代码就是想办法从jar包中读取指定的类文件,然后使用动态编译的方式将类文件加载成类对象,接着使用newInstance()创建成对象,背面就可以直接使用该对象了。
具体的SPI机制照旧有些复杂的,背面我会写1篇扩展阅读来具体解说这块,现在在这里我们就简单理解为SPI会根据某个设置文件读取指定的类路径,然后根据类路径加载到类文件,接着使用动态编译将类文件使用类加载器加载到JVM内存,并使用类对象newInstance()创建具体的对象,这样就OK了。
末了补充一点
Protocol的默认实现是Dubbo,这点在SPI的注解就可以看到:
@SPI("dubbo")public interface Protocol {}怎样看Dubbo的Protocol实现类呢,直接搜索 \META-INF\dubbo\internal\org.apache.dubbo.rpc.Protocol这些文件的设置,可以看到其中一个:
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol以是实现类就是DubboProtocol。


更多具体的内容请订阅CSDN专栏:Dubbo源码深度分析_Gemini的博客-CSDN博客
现在订阅专栏Dubbo源码深度分析_Gemini的博客-CSDN博客,发送订阅截图的私信给作者,将可以获得一份丰富的口试题整理资料,里面收录的口试题多达几百道。
页: [1]
查看完整版本: Dubbo源码深度分析:SPI机制实现