Dubbo 改进了 Java SPI 的以下问题:
JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
本文从以下几个方面,深入解析 Dubbo SPI 机制:
Dubbo SPI 特性
Dubbo SPI 的一些定义
Dubbo SPI 源码解析
Dubbo SPI 特性
扩展点自动包装
自动包装扩展点的 Wrapper 类。ExtensionLoader 在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。Wrapper类内容:
package com.alibaba.xxx; import com.alibaba.dubbo.rpc.Protocol; public class XxxProtocolWrapper implements Protocol { Protocol impl; public XxxProtocolWrapper(Protocol protocol) { impl = protocol; } // 接口方法做一个操作后,再调用extension的方法 public void refer() { //... 一些操作 impl.refer(); // ... 一些操作 } }
Wrapper 类同样实现了扩展点接口,但是 Wrapper 不是扩展点的真正实现。它的用途主要是用于从 ExtensionLoader 返回扩展点时,包装在真正的扩展点实现外。即从 ExtensionLoader 中返回的实际上是 Wrapper 类的实例,Wrapper 持有了实际的扩展点实现类。
扩展点的 Wrapper 类可以有多个,也可以根据需要新增。通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点。
扩展点自动装配
加载扩展点时,自动注入依赖的扩展点。加载扩展点时,扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader 在会自动注入依赖的扩展点。ExtensionLoader 通过扫描扩展点实现类的所有 setter 方法来判定其成员。即 ExtensionLoader 会执行扩展点的拼装操作。
示例:有两个为扩展点 CarMaker(造车者)、WheelMaker (造轮者)
public interface CarMaker { Car makeCar(); } public interface WheelMaker { Wheel makeWheel(); }
CarMaker 的一个实现类:
public class RaceCarMaker implements CarMaker { WheelMaker wheelMaker; public setWheelMaker(WheelMaker wheelMaker) { this.wheelMaker = wheelMaker; } public Car makeCar() { // ... Wheel wheel = wheelMaker.makeWheel(); // ... return new RaceCar(wheel, ...); } }
ExtensionLoader加载 CarMaker的扩展点实现RaceCar时,setWheelMaker方法的 WheelMaker也是扩展点则会注入WheelMaker的实现。
这里带来另一个问题,ExtensionLoader要注入依赖扩展点时,如何决定要注入依赖扩展点的哪个实现。在这个示例中,即是在多个WheelMaker的实现中要注入哪个。这个问题在下面一点扩展点自适应 中说明。
扩展点自适应
ExtensionLoader 注入的依赖扩展点是一个 Adaptive 实例,直到扩展点方法执行时才决定调用是一个扩展点实现。
Dubbo 使用 URL 对象(包含了Key-Value)传递配置信息。扩展点方法调用会有URL参数(或是参数有URL成员)。这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息从URL上从最外层传入。URL在配置传递上即是一条总线。
当上面执行:
// ...Wheel wheel = wheelMaker.makeWheel(url);// ...
注入的 Adaptive 实例可以提取约定 Key 来决定使用哪个 WheelMaker 实现来调用对应实现的真正的 makeWheel 方法。如提取 wheel.type, key 即 url.get("wheel.type") 来决定 WheelMake 实现。Adaptive 实例的逻辑是固定,指定提取的 URL 的 Key,即可以代理真正的实现类上,可以动态生成。
在 Dubbo 的 ExtensionLoader 的扩展点类对应的 Adaptive 实现是在加载扩展点里动态生成。指定提取的 URL 的 Key 通过 @Adaptive 注解在接口方法上提供。
Dubbo SPI 的一些定义
@SPI注解,被此注解标记的接口,就表示是一个可扩展的接口。
@Adaptive注解,有两种注解方式:一种是注解在类上,一种是注解在方法上。
1、注解在类上,而且是注解在实现类上,目前dubbo只有AdaptiveCompiler和AdaptiveExtensionFactory类上标注了此注解,这是些特殊的类,ExtensionLoader需要依赖他们工作,所以得使用此方式。
2、注解在方法上,注解在接口的方法上,除了上面两个类之外,所有的都是注解在方法上。ExtensionLoader根据接口定义动态的生成适配器代码,并实例化这个生成的动态类。被Adaptive注解的方法会生成具体的方法实现。没有注解的方法生成的实现都是抛不支持的操作异常UnsupportedOperationException。被注解的方法在生成的动态类中,会根据url里的参数信息,来决定实际调用哪个扩展。
ExtensionLoader,是dubbo的SPI机制的查找服务实现的工具类,类似与Java的ServiceLoader,可做类比。dubbo约定扩展点配置文件放在classpath下的/META-INF/dubbo,/META-INF/dubbo/internal,/META-INF/services目录下,配置文件名为接口的全限定名,配置文件内容为配置名=扩展实现类的全限定名。
比如说这段代码:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
当上面代码执行的时候,我们其实还不知道要真正使用的Protocol是什么,可能是具体的实现DubboProtocol,也可能是其他的具体实现的Protocol,那么这时候protocol到底是什么呢?protocol其实是在调用getAdaptiveExtension()方法时候,自动生成的一个类,代码如下:
package com.alibaba.dubbo.rpc;import com.alibaba.dubbo.common.extension.ExtensionLoader;public class Protocol$Adpative implements Protocol { public Invoker refer(Class arg0, URL arg1) throws Class { if (arg1 == null) throw new IllegalArgumentException("url == null"); URL url = arg1; String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])"); Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } public Exporter export(Invoker arg0) throws Invoker { if (arg0 == null) throw new IllegalArgumentException("Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("Invoker argument getUrl() == null");URL url = arg0.getUrl(); String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])"); Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName); return extension.export(arg0); } public void destroy() { throw new UnsupportedOperationException("method public abstract void Protocol.destroy() of interface Protocol is not adaptive method!"); } public int getDefaultPort() { throw new UnsupportedOperationException("method public abstract int Protocol.getDefaultPort() of interface Protocol is not adaptive method!"); } }
可以看到被@Adaptive注解的方法都生成了具体的实现,并且实现逻辑都相同。而没有被注解的方法直接抛出不支持操作的异常。
当我们使用protocol调用方法的时候,其实是调用生成的类Protocol$Adpative中的方法,这里面的方法根据url中的参数配置来找到具体的实现类,找具体实现类的方式还是通过dubbo的扩展机制。比如url中可能会有protocol=dubbo,此时就可以根据这个dubbo来确定我们要找的类是DubboProtocol。可以查看下生成的代码中getExtension(extName)这里是根据具体的名字去查找实现类。
Dubbo SPI 源码解析
了解源码结构,建立一个全局认识。结果图如下:
下面以Protocol分析扩展点的加载
//先获取ExtensionLoader实例,然后加载自适应的Protocol扩展点Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();//发布服务protocol.export(Invoker<T> invoker);
获取ExtensionLoader实例
getExtensionLoader(Protocol.class),根据要加载的接口Protocol,创建出一个ExtensionLoader实例,加载完的实例会被缓存起来,下次再加载Protocol的ExtensionLoader的时候,会使用已经缓存的这个,不会再新建一个实例:
@SuppressWarnings("unchecked") public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { //扩展点类型不能为空 if (type == null) throw new IllegalArgumentException("Extension type == null"); //扩展点类型只能是接口类型的 if(!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } //只有注解了@SPI的才会解析 if(!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
获取自适应实现
上面返回一个ExtensionLoader的实例之后,开始加载自适应实现,加载是在调用getAdaptiveExtension()方法中进行的:
第一步,从cache中获取自适应扩展点。可以关注一下,这里用了双重校验锁,Dubbo源码很多地方都用了这种方式。
@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 = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); } } } } }
第二步,缓存中不存在自适应扩展的实例,则调用createAdaptiveExtension()方法创建自适应扩展点。大家一定了解过适配器设计模式,而这个自适应扩展点实际上就是一个适配器。
private T createAdaptiveExtension() { try { //先通过getAdaptiveExtensionClass获取AdaptiveExtensionClass //然后获取其实例 //最后进行注入处理 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) {} }
第三步,getAdaptiveExtensionClass()获取自适应扩展点
private Class<?> getAdaptiveExtensionClass() { //加载当前Extension的所有实现(这里举例是Protocol,只会加载Protocol的所有实现类),如果有@Adaptive类型的实现类,会赋值给cachedAdaptiveClass //目前只有AdaptiveExtensionFactory和AdaptiveCompiler两个实现类是被注解了@Adaptive //除了ExtensionFactory和Compiler类型的扩展之外,其他类型的扩展都是下面动态创建的的实现 getExtensionClasses(); //加载完所有的实现之后,发现有cachedAdaptiveClass不为空 //也就是说当前获取的自适应实现类是AdaptiveExtensionFactory或者是AdaptiveCompiler,就直接返回,这两个类是特殊用处的,不用代码生成,而是现成的代码 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } //没有找到Adaptive类型的实现,动态创建一个 //比如Protocol的实现类,没有任何一个实现是用@Adaptive来注解的,只有Protocol接口的方法是有注解的 //这时候就需要来动态的生成了,也就是生成Protocol$Adaptive return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
第四步,getExtensionClasses()加载所有的扩展类(注意:这里加载的是类,不是初始化类的实例):
private Map<String, Class<?>> getExtensionClasses() { //从缓存中获取,cachedClasses也是一个Holder,Holder这里持有的是一个Map,key是扩展点实现名,value是扩展点实现类 //这里会存放当前扩展点类型的所有的扩展点的实现类 //这里以Protocol为例,就是会存放Protocol的所有实现类 //比如key为dubbo,value为com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol //cachedClasses扩展点实现名称对应的实现类 Map<String, Class<?>> classes = cachedClasses.get(); //如果为null,说明没有被加载过,就会进行加载,而且加载就只会进行这一次 if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { //如果没有加载过Extension的实现,进行扫描加载,完成后缓存起来 //每个扩展点,其实现的加载只会这执行一次 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
loadExtensionClasses()方法,该函数的作用就是扫描classpath底下的配置文件,加载该interface对应的所有的扩展点,并将扩展点进行分类(Adaptive,Activate),以及生成包装类等。在扫描的过程中,如果发现该扩展类为Adaptive类型,则将该class缓存到cachedAdaptiveClass中。如果所有的扩展类均不是Adaptive类型,则调用createAdaptiveExtensionClass生成一个Adaptive类型的扩展类。
private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation != null) { //当前Extension的默认实现名字 //比如说Protocol接口,注解是@SPI("dubbo") //这里dubbo就是默认的值 String value = defaultAnnotation.value(); //只能有一个默认的名字,如果多了,谁也不知道该用哪一个实现了。 if(value != null && (value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if(names.length > 1) { throw new IllegalStateException(); } //默认的名字保存起来 if(names.length == 1) cachedDefaultName = names[0]; } } //下面就开始从配置文件中加载扩展实现类 Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); //从META-INF/dubbo/internal目录下加载 loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); //从META-INF/dubbo/目录下加载 loadFile(extensionClasses, DUBBO_DIRECTORY); //从META-INF/services/下加载 loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
从各个位置的配置文件中加载实现类,对于Protocol来说加载的文件是以com.alibaba.dubbo.rpc.Protocol为名称的文件,文件的内容是:
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol com.alibaba.dubbo.rpc.protocol.http.HttpProtocol injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol memcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
loadFile()这个函数非常长,它会加载Class类。根据不同的类型加载到不同的缓存,大概的处理流程是对配置文件中的各个扩展点进行如下操作 :
判断该扩展点是否是要加载的interface的子类,如果不是则忽略
如果该class带有Adaptive的注解,则缓存到cachedAdaptiveClass中
如果该class具有拷贝构造函数,则缓存到cachedWrapperClasses中
如果该class带有Activate注解,则缓存到cachedActivates中
将所有的扩展点缓存到cachedClasses中
第五步,createAdaptiveExtensionClass() ,动态创建自适应扩展点Protocol$Adaptive。
//创建一个适配器扩展点。(创建一个动态的字节码文件) 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(); //动态编译字节码(默认情况下使用的是javassist) return compiler.compile(code, classLoader); }
第六步,创建完Protocol$Adaptive后,injectExtension()自动注入到容器。
Protocol$Adaptive的主要功能 :
1、 从url或扩展接口获取扩展接口实现类的名称
2、根据名称,获取实现类ExtensionLoader.getExtensionLoader(扩展接口类).getExtension(扩展接口实现类名称),然后调用实现类的方法。
需要明白一点dubbo的内部传参基本上都是基于Url来实现的,也就是说Dubbo是基于URL驱动的技术。所以,适配器类的目的是在运行期获取扩展的真正实现来调用,解耦接口和实现。
作者:匠丶
链接:https://www.jianshu.com/p/ac71bcd91544
共同学习,写下你的评论
评论加载中...
作者其他优质文章