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到此为止大概的走了一遍,一步一步的往里走然后再出来。整个过程还是算比较清晰的。
最后粘贴一张我简单画的时序图

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

原文地址:https://www.cnblogs.com/yxh1008/p/9308326.html