官方文档-SPI 自适应拓展
推荐阅读官方文档
JDK的SPI是一种服务发现机制。SPI将接口的实现类全限定名配置在文件中,由服务加载器读取配置文件,加载实现类,这样可以在运行期间动态的替换实现类。默认的加载路径为META-INF/services ,通过ServiceLoader 实现加载。 Dubbo实现了增强的SPI机制。Dubbo的SPI相关逻辑被封装在ExtensionLoader中,通过其加载指定的实现类。相比于JDK的路径,Dubbo将配置文件放置在META-INF/dubbo主路径下,同时采用了键值对的形式。
主要分析 com.alibaba.dubbo.common.extension.ExtensionLoader
Case-1 1 2 3 4 5 6 7 8 9 1 @Test 2 public void robotTest () {3 ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class ) ;4 Robot optimusPrime = extensionLoader.getExtension("optimusPrime" );5 optimusPrime.hi();6 7 Robot bumblebee = extensionLoader.getExtension("bumblebee" );8 bumblebee.hi();9 }
①初始化type对应ExtensionLoader对象的objectFactory成员变量 1行代码通过 ExtensionLoader.getExtensionLoader(Robot.class); 这行代码就必须走到 new ExtensionLoader(type)。 其实这1行代码就是说初始化 ExtensionFactory 的实现,默认是首先加载 ExtensionFactory.class 然后调用了 getAdaptiveExtension()获取自适应拓展
1 com.alibaba.dubbo.common.extension.ExtensionLoader.createAdaptiveExtension
这个方法就是核心需要理解的啦。
调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
通过反射进行实例化
调用 injectExtension 方法向拓展实例中注入依赖 每个步骤里面又细分,尤其是第一步比较精彩!这里面把第1步拿出来说一下。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null ) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
运行代码的时候,会发现 ExtensionFactory 包含两个实现,其中有一个被 @Adaptive 修饰的 AdaptiveExtensionFactory,其实这个在getExtensionClasses内部会初始化赋值给cachedAdaptiveClass,所以return cachedAdaptiveClass;直接返回啦。
②type对应ExtensionLoader对象获取对应名称所对应的实现类 1 Robot optimusPrime = extensionLoader.getExtension("optimusPrime" );
这个就没有啥子说的啦,自己追代码
第1个Case就是通过Class.forName加载。
dubbo通过自实现的SPI来实现扩展,配置切换。 比如Protocol、Cluster、LoadBalance 等。当某些扩展不想在框架启动时候加载, 而希望扩展方法条用的时候,根据运行时参数进行加载。 —— 扩展未加载,难道可以使用扩展的方法? dubbo通过自适应拓展机制很好的解决了,dubbo 会为拓展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段 代码得到Class类,最后再通过反射创建代理类。
@Adaptive用在类上面,基本上都是显现类使用,这个实现类就是这个接口的默认实现(比如上面说的 AdaptiveExtensionFactory);但是这个注解多数使用在方法上。
Case-2 下面的测试引用com.alibaba.dubbo.common.extensionloader.ExtensionLoader_Adaptive_Test#test_getAdaptiveExtension_inject,为了方便阅读源码,做了稍微的修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 1 @Test 2 public void test_getAdaptiveExtension_inject () throws Exception { 3 LogUtil.start(); 4 ExtensionLoader<Ext6> el = ExtensionLoader.getExtensionLoader(Ext6.class ) ; 5 Ext6 ext = el.getAdaptiveExtension(); 6 7 URL url = new URL("p1" , "1.2.3.4" , 1010 , "path1" ); 8 url = url.addParameters("ext6" , "impl1" ); 9 10 String ans = ext.echo(url, "ha" );11 assertEquals("Ext6Impl1-echo-Ext1Impl1-echo" , ans);12 13 Assert.assertTrue("can not find error." , LogUtil.checkNoError());14 LogUtil.stop();15 16 17 url = url.addParameters("simple.ext" , "impl2" );18 ans = ext.echo(url, "ha" )19 assertEquals("Ext6Impl1-echo-Ext1Impl2-echo" , ans);20 21 }
第4行代码已经在上个case分析过啦,现在分析第5行获取一个自适应的扩展。其实入口也是第4行处理ExtensionFactory加载需要走到的逻辑createAdaptiveExtension,那么为什么上面没有走到 createAdaptiveExtensionClassCode 方法呢?————是因为ExtensionFactory的实现类 AdaptiveExtensionFactory 被 @Adaptive 标注啦。
因为其被@Adaptive标注,在获取所有的拓展类getExtensionClasses逻辑中会赋值cachedAdaptiveClass成员变量。具体的赋值逻辑在loadClass方法中。
我们回头看一下在没有实现类被@Adaptive标注的情况下,Dubbo是怎做自适应扩展的?
下面再重温这段代码吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @SuppressWarnings ("unchecked" )private T createAdaptiveExtension () { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); } } private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null ) { return cachedAdaptiveClass; } return cachedAdaptiveClass = 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); } private String createAdaptiveExtensionClassCode () { }
通过createAdaptiveExtensionClassCode创建实现类的代码如下,为了方便阅读把全类名修改成简单的类名。可以断点到createAdaptiveExtensionClass中查看code变量值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.alibaba.dubbo.common.extensionloader.ext6_inject;import com.alibaba.dubbo.common.extension.ExtensionLoader;public class Ext6 $Adaptive implements Ext6 { public java.lang.String echo (URL arg0, java.lang.String arg1) { if (arg0 == null ) { throw new IllegalArgumentException("url == null" ); } URL url = arg0; String extName = url.getParameter("ext6" ); if (extName == null ) { throw new IllegalStateException("Fail to get extension(Ext6) name from url(" + url.toString() + ") use keys([ext6])" ); } Ext6 extension = ExtensionLoader.getExtensionLoader(Ext6.class ).getExtension (extName ) ; return extension.echo(arg0, arg1); } }
通过code值就能大体看出来createAdaptiveExtensionClassCode方法做了啥事。而后对这个Ext6$Adaptive类加载实例化赋值给测试代码第5行的ext变量。紧接着走到第10行,执行ext的echo()。自适应加载过程中,配置信息全部放在url中,通过url变量进行控制。其实真实执行的实现类的下面这句代码:
1 Ext6 extension = ExtensionLoader.getExtensionLoader(Ext6.class).getExtension(url.getParameter("ext6"));
因为我们url中”ext6”的参数值”impl1”,所以其实实现类就是com.alibaba.dubbo.common.extensionloader.ext6_inject.impl.Ext6Impl1。在创建的过程中需要调用 injectExtension 方法向拓展实例中注入依赖,看一下 Ext6Impl1 的定义,存在 setExt1 和 setDao 两个方法。注入的主要实现通过objectFactory.getExtension(pt, property) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // com.alibaba.dubbo.common.extension.ExtensionLoader#injectExtension Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; Object object = objectFactory.getExtension(pt, property);进行注入, if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); }
objectFactory默认实现是AdaptiveExtensionFactory,但是通过其成员变量factories依次进行加载,默认是SpiExtensionFactory加载被@SPI注解修改的类型。所以Ext6Impl1中的成员变量SimpleExt ext1会被加载。
加载的时候因为 SimpleExt 的实现类都没有带有 @Adaptive 注解,所以像 Ext6$Adaptive 形式加载。 但是因为 SimpleExt 接口定义的时候方法使用 @Adaptive,所以生成的代码逻辑比 Ext6$Adaptive 复杂。
1 2 3 4 5 6 7 8 9 10 11 12 @SPI ("impl1" )public interface SimpleExt { @Adaptive String echo (URL url, String s) ; @Adaptive ({"key1" , "key2" }) String yell (URL url, String s) ; String bang (URL url, int i) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.alibaba.dubbo.common.extensionloader.ext1;import com.alibaba.dubbo.common.extension.ExtensionLoader;public class SimpleExt $Adaptive implements SimpleExt { public String echo (URL arg0, String arg1) { if (arg0 == null ) throw new IllegalArgumentException("url == null" ); URL url = arg0; String extName = url.getParameter("simple.ext" , "impl1" ); if (extName == null ) throw new IllegalStateException("Fail to get extension(SimpleExt) name from url(" + url.toString() + ") use keys([simple.ext])" ); SimpleExt extension = (SimpleExt) ExtensionLoader.getExtensionLoader(SimpleExt.class ).getExtension (extName ) ; return extension.echo(arg0, arg1); } public String yell (URL arg0, String arg1) { if (arg0 == null ) throw new IllegalArgumentException("url == null" ); URL url = arg0; String extName = url.getParameter("key1" , url.getParameter("key2" , "impl1" )); if (extName == null ) throw new IllegalStateException("Fail to get extension(SimpleExt) name from url(" + url.toString() + ") use keys([key1, key2])" ); SimpleExt extension = (SimpleExt) ExtensionLoader.getExtensionLoader(SimpleExt.class ).getExtension (extName ) ; return extension.yell(arg0, arg1); } public String bang (URL arg0, int arg1) { throw new UnsupportedOperationException("method public abstract String SimpleExt.bang(URL,int) of interface SimpleExt is not adaptive method!" ); } }
实现带有制定的默认值,参数的设置也是有规则的,所以建议阅读 createAdaptiveExtensionClassCode 方法,阅读的时候参考开头推荐的官网源码就很简单。
最后构造好实例执行 echo 方法。
这里就可以看出来URL的作用,相当于数据总线,通过其绑定配置信息
总结
熟悉 ExtensionLoader.getExtensionLoader(type) 这种俄罗斯套娃式的加载。
v1.5.2