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,是就抛出异常
-
判断扩展type是否为接口,不是接口抛出异常
-
判断扩展type是对应的接口是否带有SPI注解,没有抛出异常
-
从缓存中去根据扩展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()); }
代码逻辑,
-
首次进来type为interface com.alibaba.dubbo.container.Container,与ExtensionLoader.class不相等,所以会执行后边的逻辑,再次执行.getExtensionLoader方法,将其加入缓存当中
-
调用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;
}
处理逻辑
-
从缓存中获取实例
-
因为该实例的单例模式,采用了双重检查锁模式,对该实例做了两次null的判断
-
调用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); } }
处理逻辑
-
首先调用getAdaptiveExtensionClass方法,然后获取实例
-
然后将获取的实例最为参数传入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
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到此为止大概的走了一遍,一步一步的往里走然后再出来。整个过程还是算比较清晰的。
最后粘贴一张我简单画的时序图
欢迎扫码关注我的微信公众号,我会定期的更新一些个人技术文章