这篇文章主要介绍“如何理解Dubbo的SPI自适应”,在日常操作中,相信很多人在如何理解Dubbo的SPI自适应问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何理解Dubbo的SPI自适应”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
先定义一个SPI接口(被@SPI标注):
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Adaptive; import org.apache.dubbo.common.extension.SPI; @SPI public interface SpiIf { @Adaptive void test1(URL url); @Adaptive void test2(ObjHasUrl ohu); void test3(URL url); void test4(String name); }
这里的ObjHasUrl是一个内部有URL属性的对象,为什么要有URL属性的原因下面会说到。
下一步,定义两个实现类Spi1和Spi2:
public class Spi1 implements SpiIf { @Override public void test1(URL url) { System.out.println("This is Spi1:test1"); } @Override public void test2(ObjHasUrl ohu) { System.out.println("This is Spi1:test2"); } @Override public void test3(URL url) { System.out.println("This is Spi1:test3"); } @Override public void test4(String name) { System.out.println("This is Spi1:test4"); } } public class Spi2 implements SpiIf { @Override public void test1(URL url) { System.out.println("This is Spi2:test1"); } @Override public void test2(ObjHasUrl ohu) { System.out.println("This is Spi2:test2"); } @Override public void test3(URL url) { System.out.println("This is Spi2:test3"); } @Override public void test4(String name) { System.out.println("This is Spi2:test4"); } }
最后一步,定义一个Runner测试启动器:
public class Runner { public static void main(String[] args) { URL url = new URL("dubbo", "123", 999); url = url.addParameter("spi.if", "S2"); //设置url值,来获取 SpiTest的自适应扩展 S2。 SpiIf spiIf = ExtensionLoader.getExtensionLoader(SpiIf.class).getAdaptiveExtension(); spiIf.test1(url); ObjHasUrl ohu = new ObjHasUrl(url); spiIf.test2(ohu); url = url.addParameter("spi.if", "S1"); SpiIf spiIf2 = ExtensionLoader.getExtensionLoader(SpiIf.class).getAdaptiveExtension(); spiIf.test2(ohu); } }
以上类可以直接拷贝到自己的本地工程DEBUG用。
当我们启功Runner的时候,第一步先看getExtensionLoader方法,这里开始进入Dubbo的代码:
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 an interface!"); } //验证是否有SPI注解 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } 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对象,主要是对接口类做一些验证,确认是扩展点(有SPI注解)。
第二步,进入ExtensionLoader的getAdaptiveExtension方法:
public T getAdaptiveExtension() { //先找缓存 Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError != null) { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } //缓存没有,则开始创建 synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } return (T) instance; }
这一步主要是获取接口的实现对象实列,继续分析实际创建扩展点的方法:
private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
这一步拆分成两步,第一步:getAdaptiveExtensionClass,第二步:injectExtension
第一步:
private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); } private Class<?> createAdaptiveExtensionClass() { String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); }
重点来了,创建class的时候会生成一个code(实际是SpiIf$Adaptive->SpiIf一个实现类):
import org.apache.dubbo.common.extension.ExtensionLoader; public class SpiIf$Adaptive implements SpiIf { public void test2(com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl arg0) { if (arg0 == null) throw new IllegalArgumentException("com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter("spi.if"); if(extName == null) throw new IllegalStateException("Failed to get extension (com.zf.server.authserver.spi.dubbospitest2.SpiIf) name from url (" + url.toString() + ") use keys([spi.if])"); com.zf.server.authserver.spi.dubbospitest2.SpiIf extension = (com.zf.server.authserver.spi.dubbospitest2.SpiIf)ExtensionLoader.getExtensionLoader(com.zf.server.authserver.spi.dubbospitest2.SpiIf.class).getExtension(extName); extension.test2(arg0); } public void test1(org.apache.dubbo.common.URL arg0) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("spi.if"); if(extName == null) throw new IllegalStateException("Failed to get extension (com.zf.server.authserver.spi.dubbospitest2.SpiIf) name from url (" + url.toString() + ") use keys([spi.if])"); com.zf.server.authserver.spi.dubbospitest2.SpiIf extension = (com.zf.server.authserver.spi.dubbospitest2.SpiIf)ExtensionLoader.getExtensionLoader(com.zf.server.authserver.spi.dubbospitest2.SpiIf.class).getExtension(extName); extension.test1(arg0); } public void test3(org.apache.dubbo.common.URL arg0) { throw new UnsupportedOperationException("The method public abstract void com.zf.server.authserver.spi.dubbospitest2.SpiIf.test3(org.apache.dubbo.common.URL) of interface com.zf.server.authserver.spi.dubbospitest2.SpiIf is not adaptive method!"); } public void test4(java.lang.String arg0) { throw new UnsupportedOperationException("The method public abstract void com.zf.server.authserver.spi.dubbospitest2.SpiIf.test4(java.lang.String) of interface com.zf.server.authserver.spi.dubbospitest2.SpiIf is not adaptive method!"); } }
仔细一看,其实就是为我们的接口生成了一个实现类。然后为Adaptive注解标注的方法生成了实际的内容(就是根据URL参数来获取实际的扩展类),这也解释的Adaptive注解的实际作用。
还有一点需要注意:test1提供的是URL的参数,test2提供的是包含URL属性的对象。它们的共同点就是都包含了一个URL,如果不提供会提示没有URL异常。具体原因可以自行分析以下方法:
new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
最终在加载生成的实现类。
第二步:injectExtension
private T injectExtension(T instance) { if (objectFactory == null) { return instance; } try { for (Method method : instance.getClass().getMethods()) { if (!isSetter(method)) { continue; } if (method.getAnnotation(DisableInject.class) != null) { continue; } Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } try { String property = getSetterProperty(method); Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }
而这一步干啥呢?简单说就是循环set方法,如果参数类型也是一个自适应扩展点的话,继续上面的步骤拿到扩展点对象并反射注入,实现了Dubbo版的依赖注入。
至此,返回最终生成的对象-> SpiIf$Adaptive的实例并缓存在cachedAdaptiveInstance中,在Runner中就会根据url对应的参数值来获取对应的扩展类。
总结:
1、自适应扩展接口需要 SPI注解,方法需要Adaptive注解,Adaptive方法需要URL参数或者是有URL属性的对象参数;
2、最终会返回接口实现类对象 SpiIf$Adaptive,里面封装了根据url参数来获取扩展对象的方法;
到此,关于“如何理解Dubbo的SPI自适应”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。