• 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的官网。http://dubbo.apache.org/#/docs/dev/SPI.md?lang=zh-cn
    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方法的代码如下,这个方法代码很长
      1   private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
      2         String fileName = dir + type.getName();
      3         try {
      4             Enumeration<java.net.URL> urls;
      5             //获取类加载器
      6             ClassLoader classLoader = findClassLoader();
      7             if (classLoader != null) {
      8                 urls = classLoader.getResources(fileName);
      9             } else {
     10                 urls = ClassLoader.getSystemResources(fileName);
     11             }
     12             if (urls != null) {
     13                 while (urls.hasMoreElements()) {
     14                     java.net.URL url = urls.nextElement();
     15                     try {
     16                         BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
     17                         try {
     18                             String line = null;
     19                             while ((line = reader.readLine()) != null) {
     20                                 final int ci = line.indexOf('#');
     21                                 if (ci >= 0) line = line.substring(0, ci);
     22                                 line = line.trim();
     23                                 if (line.length() > 0) {
     24                                     try {
     25                                         String name = null;
     26                                         int i = line.indexOf('=');
     27                                         if (i > 0) {
     28                                             name = line.substring(0, i).trim();
     29                                             line = line.substring(i + 1).trim();
     30                                         }
     31                                         if (line.length() > 0) {
     32                                             Class<?> clazz = Class.forName(line, true, classLoader);
     33                                             if (! type.isAssignableFrom(clazz)) {
     34                                                 throw new IllegalStateException("Error when load extension class(interface: " +
     35                                                         type + ", class line: " + clazz.getName() + "), class " 
     36                                                         + clazz.getName() + "is not subtype of interface.");
     37                                             }
     38                                             if (clazz.isAnnotationPresent(Adaptive.class)) {
     39                                                 if(cachedAdaptiveClass == null) {
     40                                                     cachedAdaptiveClass = clazz;
     41                                                 } else if (! cachedAdaptiveClass.equals(clazz)) {
     42                                                     throw new IllegalStateException("More than 1 adaptive class found: "
     43                                                             + cachedAdaptiveClass.getClass().getName()
     44                                                             + ", " + clazz.getClass().getName());
     45                                                 }
     46                                             } else {
     47                                                 try {
     48                                                     clazz.getConstructor(type);
     49                                                     Set<Class<?>> wrappers = cachedWrapperClasses;
     50                                                     if (wrappers == null) {
     51                                                         cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
     52                                                         wrappers = cachedWrapperClasses;
     53                                                     }
     54                                                     wrappers.add(clazz);
     55                                                 } catch (NoSuchMethodException e) {
     56                                                     clazz.getConstructor();
     57                                                     if (name == null || name.length() == 0) {
     58                                                         name = findAnnotationName(clazz);
     59                                                         if (name == null || name.length() == 0) {
     60                                                             if (clazz.getSimpleName().length() > type.getSimpleName().length()
     61                                                                     && clazz.getSimpleName().endsWith(type.getSimpleName())) {
     62                                                                 name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
     63                                                             } else {
     64                                                                 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
     65                                                             }
     66                                                         }
     67                                                     }
     68                                                     String[] names = NAME_SEPARATOR.split(name);
     69                                                     if (names != null && names.length > 0) {
     70                                                         Activate activate = clazz.getAnnotation(Activate.class);
     71                                                         if (activate != null) {
     72                                                             cachedActivates.put(names[0], activate);
     73                                                         }
     74                                                         for (String n : names) {
     75                                                             if (! cachedNames.containsKey(clazz)) {
     76                                                                 cachedNames.put(clazz, n);
     77                                                             }
     78                                                             Class<?> c = extensionClasses.get(n);
     79                                                             if (c == null) {
     80                                                                 extensionClasses.put(n, clazz);
     81                                                             } else if (c != clazz) {
     82                                                                 throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
     83                                                             }
     84                                                         }
     85                                                     }
     86                                                 }
     87                                             }
     88                                         }
     89                                     } catch (Throwable t) {
     90                                         IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
     91                                         exceptions.put(line, e);
     92                                     }
     93                                 }
     94                             } // end of while read lines
     95                         } finally {
     96                             reader.close();
     97                         }
     98                     } catch (Throwable t) {
     99                         logger.error("Exception when load extension class(interface: " +
    100                                             type + ", class file: " + url + ") in " + url, t);
    101                     }
    102                 } // end of while urls
    103             }
    104         } catch (Throwable t) {
    105             logger.error("Exception when load extension class(interface: " +
    106                     type + ", description file: " + fileName + ").", t);
    107         }
    108     }
    109    
    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到此为止大概的走了一遍,一步一步的往里走然后再出来。整个过程还是算比较清晰的。
    最后粘贴一张我简单画的时序图

    欢迎扫码关注我的微信公众号,我会定期的更新一些个人技术文章

  • 相关阅读:
    error C2065: 'IDD_DIALOG1' : undeclared identifier
    MySQL API函数
    MFC连接MySQL C API方法
    MFC连接MySQL
    MFC连接MySQL数据库方法
    error C4430: error 2141
    GetLastError
    char与CString相互转换
    处理图片(获取图片详细信息)大小 格式
    RGB颜色值 十六进制颜色码 转换
  • 原文地址:https://www.cnblogs.com/yxh1008/p/9308326.html
Copyright © 2020-2023  润新知