【干货】Dubbo死磕之扩展点加载ExetnsionLoader

正文

回到顶部

dubbo的SPI机制与JDK的SPI机制对比     

       dubbo一款阿里一款开源的RPC框架,他本身是一款非常复杂的系统,我们主要针对里边的一些核心点来展开分析,其中duboo里的一种核心机制叫SPI( Service Provider Interface)服务发现机制,他是基于原生jdk的SPI机制演化而来。在分析duboo的ExtensionLoader之前,我们先大致了解一下标准JDK的SPI机制。一个最经典的JDK的SPI机制,就是java数据库驱动JDBC,其实JDK自带的jdbc 驱动并没有实现对数据库的封装,而是开放了一系列的接口供各大厂商调用。

了解完了JDK的SPI基本功能之后,来分析一下dubbo的ExtensionLoader,以下是摘自dubbo的官网。https://dubbo.apache.org/zh/

Dubbo 改进了 JDK 标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName()获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

在分析dubbo的扩展点加载机制ExtensionLoader加载机制之前,先给大家看一下duboo的层级构成(源自dubbo官网)

  •        config,配置层,对外配置接口,以ServiceConfig, ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类

  • proxy,服务代理层,服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory

  • registry,注册中心层,封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory, Registry, RegistryService

  • cluster,路由层,封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster, Directory, Router, LoadBalance

  • monitor,监控层,RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory, Monitor, MonitorService

  • protocol,远程调用层,封将RPC调用,以Invocation, Result为中心,扩展接口为Protocol, Invoker, Exporter

  • exchange,信息交换层,封装请求响应模式,同步转异步,以Request, Response为中心,扩展接口为Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer

  • transport,网络传输层,抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel, Transporter, Client, Server, Codec

  •         serialize,数据序列化层,可复用的一些工具,扩展接口为Serialization, ObjectInput, ObjectOutput, ThreadPool

我们可以看到,dubbo的源码每个层级,不只引用了一个扩展方式,而是提供了多种扩展方式,如果我们把dubbo的源码层级整合成一个整体来看,把他当作一个平面,那么他上面每个层级的扩展实现方式就好比一个槽,这些槽可以供用户自主选择实现合适的扩展方式。

好了,现在我们话不多说了,开始分析duboo启动的时候加载ExtensionLoader,以这种方式来分析ExtensionLoader加载机制。

回到顶部

getExtensionLoader

首先,ExtensionLoader是dubbo的一个类,dubbo器的时候首先会加载ExtensionLoader里面的静态方法getExtensionLoader。

复制代码

@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//首次进来,初始化的type是默认的扩展点interface com.alibaba.dubbo.container.Container
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if(!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//根据这个type去缓存中获取loader
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//这里会去new一个ExtensionLoader
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}

复制代码

这里主要是做了一些初始化的合法性校验操作

判断扩展type是否为null,是就抛出异常

  1. 判断扩展type是否为接口,不是接口抛出异常

  2. 判断扩展type是对应的接口是否带有SPI注解,没有抛出异常

  3. 从缓存中去根据扩展type获取ExtensionLoader,如果没有获取到,会先new一个ExtensionLoader,然后加他push到缓存,否则直接返回

进行完合法性校验操作完之后,会从缓存中去获取扩展机制,没有会new一个扩展机制,然后会到ExtensionLoader的私有构造器中。

复制代码

private ExtensionLoader(Class<?> type) {
//首次进来这个type等于interface com.alibaba.dubbo.container.Container,所以会执行后面的ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
//其中ExtensionLoader.getExtensionLoader(ExtensionFactory.class)通过,
//再次进来的时候type变为了interface com.alibaba.dubbo.common.extension.ExtensionFactory,就会直接返回
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

复制代码

代码逻辑,

  1. 首次进来type为interface com.alibaba.dubbo.container.Container,与ExtensionLoader.class不相等,所以会执行后边的逻辑,再次执行.getExtensionLoader方法,将其加入缓存当中

  2. 调用getAdaptiveExtension方法,获得自适应扩展点,将获取到的实例复制给objectFactory。

回到顶部

getAdaptiveExtension

然后进入getAdaptiveExtension函数,分析该方法是如何获取自适应扩展点的。

复制代码

@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
     //从缓存中获取实例
     Object instance = cachedAdaptiveInstance.get();
     //双重检查锁,和单点登陆里的双重检查锁类似
     if (instance == null) {
         if(createAdaptiveInstanceError == null) {
             synchronized (cachedAdaptiveInstance) {
                 instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                       try {
                           //创建自适应扩展点,获得的实例复制给instance
                           instance = createAdaptiveExtension();
                          //将回去的自适应扩展点实例加入缓存
                          cachedAdaptiveInstance.set(instance);
                           } catch (Throwable t) {
                                  createAdaptiveInstanceError = t;
                                  throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                           }
                      }
                    }
                }
      else {
              throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
     }
}

return (T) instance;

}

复制代码

处理逻辑

  1. 从缓存中获取实例

  2. 因为该实例的单例模式,采用了双重检查锁模式,对该实例做了两次null的判断

  3. 调用createAdaptiveExtension方法创建自适应扩展点,将获得的实例复制给instance,并加入缓存

回到顶部

createAdaptiveExtension

第3点里的createAdaptiveExtension的代码如下

复制代码

@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
     try {
          return injectExtension((T) getAdaptiveExtensionClass().newInstance());
      } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
      }
}

复制代码

处理逻辑

  1. 首先调用getAdaptiveExtensionClass方法,然后获取实例

  2. 然后将获取的实例最为参数传入injectExtension方法,将结果返回

回到顶部

getAdaptiveExtensionClass

第1点的getAdaptiveExtensionClass方法代码如下

复制代码

private Class<?> getAdaptiveExtensionClass() {
       getExtensionClasses();
      if (cachedAdaptiveClass != null) {
          return cachedAdaptiveClass;
       }
       return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

复制代码

根据代码可以发现,我们先不管getExtensionClasses方法里做了什么事情,先看主线,cachedAdaptiveClass不为null的话就直接返回,为null的话就先创建再返回。

回到顶部

createAdaptiveExtensionClass

我们先分析createAdaptiveExtensionClass方法,然后再去分析getExtensionClasses,createAdaptiveExtensionClass方法的代码如下。

复制代码

//基于动态代理生成一个动态的字节码文件
private Class<?> createAdaptiveExtensionClass() {
    //生成字节码代码
    String code = createAdaptiveExtensionClassCode();
    //获得类加载器
    ClassLoader classLoader = findClassLoader();
    //再次创建一个自适应扩展点
  com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    //动态编译字节码
   return compiler.compile(code, classLoader);
}

复制代码

回到顶部

createAdaptiveExtensionClassCode

该方法主要是通过动态代理,基于javassist方式实现,生成二进制字节码代码文本,然后获得类加载器,再次获取一个compiler自适应扩展点,生成一个编译器,然后动态编译字节码文本,生成动态的类。现在主要分析一下createAdaptiveExtensionClassCode是如何生成字节码代码的,通过dubug模式,获取到code的文本,整理一下的代码如下

复制代码

 1 package com.alibaba.dubbo.rpc;
 2  
 3 import com.alibaba.dubbo.common.extension.ExtensionLoader; 
 4 public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { 
 5 public void destroy() { 
 6 throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.
 7 Protocol.destroy() of interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!");
 8 }
 9  
10 public int getDefaultPort() { 
11 throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.
12 rpc.Protocol.getDefaultPort()of interface com.alibaba.dubbo.rpc.Protocol is not adaptive
13 method!");
14 } 
15  
16 public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.
17 alibaba.dubbo.rpc.Invoker { 
18 if (arg0 == null) 
19 throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
20 if (arg0.getUrl() =null) 
21 throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
22 com.alibaba.dubbo.common.URL url = arg0.getUrl();
23 String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); 
24 if(extName == null)
25 throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol)
26 name from url(" + url.toString() + ") use keys([protocol])");
27 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.
28 getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
29 return extension.export(arg0);
30 } 
31  
32 public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws 
33 java.lang.Class { 
34 if (arg1 == null) 
35 throw new IllegalArgumentException("url == null");
36 com.alibaba.dubbo.common.URL url = arg1;
37 String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); 
38 if(extName == null) 
39 throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol)
40 name from url(" + url.toString() + ") use keys([protocol])");
41 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.
42 getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
43 return extension.refer(arg0, arg1);
44 }
45 }

复制代码

通过代码可以发现,这里默认的RPC方案就是dubbo的自身方案,包括服务的暴露以及引用

String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); 

回到顶部

getExtensionClasses

到这里,一个扩展点的加载到此结束,我们再回过头来分析getExtensionClasses方法具体做了什么事情。

复制代码

//加载扩展点 的实现类
private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
       if (classes == null) {
          synchronized (cachedClasses) {
             classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
       }
}
return classes;
}

复制代码

回到顶部

loadExtensionClasses

这里又用到了双重检查锁机制,两次判断classes是否为null,然后再调用loadExtensionClasses加载扩展点实现类

复制代码

 1 // 此方法已经getExtensionClasses方法同步过。
 2 private Map<String, Class<?>> loadExtensionClasses() {
 3     final SPI defaultAnnotation = type.getAnnotation(SPI.class);
 4     if(defaultAnnotation != null) {
 5         String value = defaultAnnotation.value();
 6     if(value != null && (value = value.trim()).length() > 0) {
 7        String[] names = NAME_SEPARATOR.split(value);
 8     if(names.length > 1) {
 9        throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
10        + ": " + Arrays.toString(names));
11      }
12     if(names.length == 1) cachedDefaultName = names[0];
13     }
14 }
15 Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
16 loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
17 loadFile(extensionClasses, DUBBO_DIRECTORY);
18 loadFile(extensionClasses, SERVICES_DIRECTORY);
19 return extensionClasses;
20 }

复制代码

回到顶部

loadFile

loadFile方法根据DUBBO_INTERNAL_DIRECTORY、DUBBO_DIRECTORY、SERVICES_DIRECTORY的路劲去寻找各自的配置文件, 类似于jdk的SPI中放在META-INF/services目录下的文件。loadFiles方法的代码如下,这个方法代码很长

 View Code

回到顶部

injectExtension

该方法没有什么很好讲的,里面就是一些if-else,主要是将扩展点的配置文件加载出来或者放入缓存当中。

到此,我们将目光再往前退一下,我们之前是不是调用了getAdaptiveExtensionClass方法,到此为止,我们的仅仅是调用完成了getAdaptiveExtensionClass方法,然后将getAdaptiveExtensionClass的实例当作参数,传入injectExtension方法,我们再看一下injectExtension的代码

复制代码

 1 private T injectExtension(T instance) {
 2     try {
 3          //getExtensionLoader的时候赋值的
 4         if (objectFactory != null) {
 5             for (Method method : instance.getClass().getMethods()) {
 6                 //判断是否以set开头,以set方式动态注入
 7                 if (method.getName().startsWith("set")
 8                     && method.getParameterTypes().length == 1
 9                        && Modifier.isPublic(method.getModifiers())) {
10                          //获取set方法的参数类型
11                          Class<?> pt = method.getParameterTypes()[0];
12                         try {
13                               String property = method.getName().length() > 3 ? method.getName().substring(3, 4).
14                               toLowerCase() + method.getName().substring(4) : "";
15                              //根据类型名称获得对应的扩展点
16                              Object object = objectFactory.getExtension(pt, property);
17                             if (object != null) {
18                                 method.invoke(instance, object);
19                               }
20                          } catch (Exception e) {
21                             logger.error("fail to inject via method " + method.getName()
22                            + " of interface " + type.getName() + ": " + e.getMessage(), e);
23                          }
24                        }
25                }
26           }
27      } catch (Exception e) {
28      logger.error(e.getMessage(), e);
29 }
30 return instance;
31 }

复制代码

这里可以看到,扩展点自动注入的一句就是根据setter方法对应的参数类型和property名称从ExtensionFactory中查询,如果有返回扩展点实例,那么就进行注入操作。到这里getAdaptiveExtension方法就分析完毕了。

回到顶部

总结

ExtensionLoader到此为止大概的走了一遍,一步一步的往里走然后再出来。整个过程还是算比较清晰的。

最后粘贴一张我简单画的时序图

 

转自:Dubbo死磕之扩展点加载ExetnsionLoader - 人生无设限 - 博客园 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值