懂得Dubbo的同学一听到Dubbo肯定会有两个反应:1.Dubbo是高性能RPC调用的框架;2.Dubbo的SPI机制是Dubbo实现插件式扩展的基础;
而Dubbo中有一个重要的设计理念就是微内核、富插件模式。所谓的微内核、富插件就是内核提供特定的接口,而组件则只需要满足接口的特定要求就可以灵活的接入,并且管理这些接入的插件,但是这些组件可以独立的发展、更改(不会对现有系统造成改动)。开发中常用的idea就有插件市场,还有像VSCode等,我们都可以从插件市场中选择需要的插件安装即可使用。
那对于高度扩展性的设计理念Dubbo是怎么实现的呢?官网中Dubbo的扩展点高达几十个!!!
![5c60085970a4c3872730d5688e58f885.png](https://img-blog.csdnimg.cn/img_convert/5c60085970a4c3872730d5688e58f885.png)
图1:SPI扩展实现
Java SPI使用以及不足之处
再说Dubbo SPI前,我们还是有必要先说一下Java的SPI机制(做一下预热,因为Dubbo的SPI机制功能更加强大也更加复杂);
对于Java SPI的使用是比较简单的(Java SPI实现原理以后有时间的话可以给大家再说一下);
1.首先我们肯定是要先定义扩展接口;
public interface Computer { // 打开电脑 void start();}
2.我们再需要定义一些实现类,我这里实现两个意思一下;
public class Mac implements Computer { @Override public void start() { System.out.println("你的mac电脑正在打开......"); }}public class Thinkpad implements Computer { @Override public void start() { System.out.println("你的Thinkpad电脑正在打开......"); }}
3.然后再在项目reources目录下新建一个META-INF/services文件夹,然后再新建一个以Computer接口的全限定名命名的文件,文件内容如下所示;
com.demo.spi.Maccom.demo.spi.Thinkpad
4.上面的步骤就类似于烧饭时我们的原材料已经准备完毕,下面我们需要再使用锅进行炒菜;这边的一个关键就是使用ServiceLoader的load方法去创建对应的服务加载器,然后通过服务加载器去加载实例;
ServiceLoader serviceLoader = ServiceLoader.load(Computer.class);Iterator iterator = loadedParsers.iterator();while (iterator.hasNext()){ Computer computer = iterator.next(); Computer.start();}
从上面的形式可以看出来原生的Java SPI机制缺点就是会一次性加载所有的扩展实现,如果有的扩展吃实话很耗时,但也没用上很浪费资源。
Dubbo SPI实现逻辑
现在回到正题就是Dubbo是如何对Java的SPI机制进行改进的?我这里总结以下几点:
1.Java SPI 会一次性实例化扩展点所有实现,但是Dubbo可以实现按需加载实现类;
2.如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
3.增加了对扩展点IOC和AOP的支持,一个扩展点可以直接setter 注入其它扩展点。
4.Dubbo的扩展机制能很好的支持第三方IoC容器,默认支持Spring Bean。
在以上改进项中,第一个改进项比较好理解。第二个改进项没有进行验证,就不多说了。第三、四个改进项是增加了对 IOC 和 AOP 的支持。
下面就说一下Dubbo的SPI实现原理(Dubbo SPI使用我这边就不仔细说了,之前Dubbo负载负载均衡和容错机制文章中都有提到):
1.我们首先通过ExtensionLoader(我们叫ExtensionLoader为扩展点加载器)的getExtensionLoader方法获取一个ExtensionLoader实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。
T t = ExtensionLoader.getExtensionLoader(T.class).getExtendsion();
getExtensionLoader(...)用于判断接口上是否有@SPI注解(扩展点)和类型必须是接口。
public static ExtensionLoader getExtensionLoader(Class type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } else if (!type.isInterface()) { // 是否是接口 throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } else if (!withExtensionAnnotation(type)) { // 是否标记有SPI注解 throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } else { // 这里加了缓存机制 ExtensionLoader loader = (ExtensionLoader)EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); loader = (ExtensionLoader)EXTENSION_LOADERS.get(type); } return loader; }}
2. 根据扩展实现类的别名name调用getExtension(String name)方法获取扩展对象(如果传入的name为"true",则获取默认的扩展实现类实例,即通过@SPI注解的value属性指定的默认扩展实现类别名,此时如果未设置value值则返回 null)。
public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } else if ("true".equals(name)) { // 默认扩展实现对象 return this.getDefaultExtension(); } else { Holder holder = this.getOrCreateHolder(name); Object instance = holder.get(); if (instance == null) { synchronized(holder) { instance = holder.get(); // 创建扩展实现类 if (instance == null) { instance = this.createExtension(name); holder.set(instance); } } } return instance; }}
3.创建扩展实现类主要分为以下几个步骤:
3.1.加载扩展实现类;
3.2.反射创建扩展实现类的实例对象;
3.3.实例对象属性注入;
3.4.遍历包装类列表 cachedWrapperClasses,创建包装类实例,并注入依赖。
private T createExtension(String name) { // 加载扩展实现类clazz(这里也是有Map缓存的) Class> clazz = (Class)this.getExtensionClasses().get(name); if (clazz == null) { throw this.findException(name); } else { try { // 反射创建扩展实现类的实例对象(这里也是有Map缓存) T instance = EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = EXTENSION_INSTANCES.get(clazz); } // 注入实例对象属性值 this.injectExtension(instance); // 遍历包装类列表 cachedWrapperClasses,创建包装类实例,并注入依赖 Set> wrapperClasses = this.cachedWrapperClasses; Class wrapperClass; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for(Iterator var5 = wrapperClasses.iterator(); var5.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) { wrapperClass = (Class)var5.next(); } } this.initExtension(instance); return instance; } catch (Throwable var7) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + this.type + ") couldn't be instantiated: " + var7.getMessage(), var7); } }}
Dubbo中SPI加载路径(ExtensionLoader中直接可以看到):
META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/
其实到这里Dubbo的SPI实现逻辑基本就已经结束了,但是Dubbo为了动态化、可以实时选择不同的扩展类又引入了一套自适应扩展机制。
Dubbo自适应扩展机制
Dubbo的自适应扩展机制从本质上的实现原理还是代理模式。
首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,在代理类中,就可以通过URL对象的参数来确定到底调用哪个实现类。
自适应扩展使用(以负载均衡为例):
// 先在SPI基础上,在需要的方法或者类加上@Adaptive注解@SPI("dubbo")public interface Protocol { int getDefaultPort(); @Adaptive Exporter export(Invoker invoker) throws RpcException; @Adaptive Invoker refer(Class type, URL url) throws RpcException;// 获取动态拓展对象ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
在getAdaptiveExtension中会进行javassist动态编译,编译完之后就可以根据入参的URL进行判断选择哪个Name,再进行ExtensionLoader.getExtensionLoader(Class).getExtension(name),正常调用;
// 进行动态编译private Class> createAdaptiveExtensionClass() { String code = (new AdaptiveClassCodeGenerator(this.type, this.cachedDefaultName)).generate(); ClassLoader classLoader = findClassLoader(); Compiler compiler = (Compiler)getExtensionLoader(Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader);}
我们直接看一下编译后的代码大致的样子:
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol { // ...... // 典型的代理模式 public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } // ......}
到这里Dubbo的整套SPI机制已经将完,大家可以试着自己基于Spring做一套Spring SPI机制,友情提示一下可以基于FactoryBean、代理模式和Map做缓存。