這篇文章主要介紹“如何理解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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。