dubbo如何實現可擴充套件的,援引官網描述:
Dubbo 的擴充套件點載入從 JDK 標準的 SPI (Service Provider Interface) 擴充套件點發現機制加強而來。
Dubbo 改進了 JDK 標準的 SPI 的以下問題:
- JDK 標準的 SPI 會一次性例項化擴充套件點所有實現,如果有擴充套件實現初始化很耗時,但如果沒用上也載入,會很浪費資源。
- 如果擴充套件點載入失敗,連擴充套件點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過
getName()
獲取指令碼型別的名稱,但如果 RubyScriptEngine 因為所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類載入失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當使用者執行 ruby 指令碼時,會報不支援 ruby,而不是真正失敗的原因。 - 增加了對擴充套件點 IoC 和 AOP 的支援,一個擴充套件點可以直接 setter 注入其它擴充套件點。
定義個介面:
public interface HelloService { String sayHello(); }
定義兩個實現類:
public class DogHelloService implements HelloService { @Override public String sayHello() { return "wang"; } }
public class HumanHelloService implements HelloService { @Override public String sayHello() { return "hello 你好"; } }
1.JDK標準的SPI是怎麼回事?
ServiceLoader.load方法會載入META-INF/services/目錄下定義的介面全限定名檔案,內容為實現類。
com.exm.service.impl.DogHelloService
com.exm.service.impl.HumanHelloService
核心為ServiceLoader.LazyIterator迭代器,在load方法被呼叫時,會初始化該迭代器,如下:
public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
LazyIterator會讀取配置實現類,並通過反射進行例項化(前提要求實現類需要具備無參構造)。
其中hasNextService方法會載入META-INF/services介面檔案,並載入到Enumeration<URL> configs中,原始碼如下:
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { //獲取配置全路徑名。如:META-INF/services/com.exm.service.HelloService String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else //並載入到Enumeration<URL> configs中 configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }
迭代器通過nextService獲取下一個實現類物件,原始碼如下,其中包含反射拿到Class物件,並例項化。
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { //反射例項化 c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { //轉強制化為介面,並放入LinkedHashMap<String,S> providers中 S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
為什麼說JDK的SPI會一次性載入並例項化所有的擴充套件呢?
1⃣️如果其中有一個例項化或cast時異常,後邊所有都將無法遍歷。
2⃣️如果某個類的例項化耗時很長,並沒用到,會造成資源浪費
編寫一個測試方法:
public static void main(String[] args) { final ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class); for (HelloService helloService : helloServices){ System.out.println(helloService.getClass().getName() + ":" + helloService.sayHello()); } }
測試輸出:
com.exm.service.impl.DogHelloService:wang
com.exm.service.impl.HumanHelloService:hello 你好
2.Dubbo是怎麼進行改進的呢?
dubbo時如何進行改進的呢?
(1)dubbo定義的SPi檔案包含了key,即每個實現類對應一個不同的key,在載入class的時候,會將key和class放入一個map中。
這樣在使用者想使用哪個類的例項時,只需要例項化對應的類,無需例項化所有類
(2)Adaptive功能:實現動態的使用擴充套件點。通過 getAdaptiveExtension方法 統一對指定介面對應的所有擴充套件點進行封裝,通過URL的方式對擴充套件點來進行動態選擇。
2.1 載入所有擴充套件點,選擇性例項化
public class Main { public static void main(String[] args) { // 獲取擴充套件載入器 ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class); // 遍歷所有的支援的擴充套件點,並將key與實現類進行關聯 Set<String> extensions = extensionLoader.getSupportedExtensions(); for (String extension : extensions){ String result = extensionLoader.getExtension(extension).sayHello(); System.out.println(result); } } }
然後通過extensionLoader.getExtension對指定key進行例項化,這一點是與JDK不同的。
我們看下具體是怎麼載入的,getSupportedExtensions為入口,最終會通過loadDirectory進行載入
Set<String> getSupportedExtensions() Map<String, Class<?>> getExtensionClasses() Map<String, Class<?>> loadExtensionClasses() void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst)
(1)getSupportedExtensions方法會返回所有擴充套件點的key,供使用者使用。
public Set<String> getSupportedExtensions() { Map<String, Class<?>> clazzes = this.getExtensionClasses(); //獲取到所有擴充套件點後,將key放入TreeSet中(按字串排序) return Collections.unmodifiableSet(new TreeSet(clazzes.keySet())); }
loadDirectory載入檔案的來源為以下6個部分,相容了JDK路徑。
同時載入有順序,越靠前越優先載入
private Map<String, Class<?>> loadExtensionClasses() { this.cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap(); this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName(), true); this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName().replace("org.apache", "com.alibaba"), true); this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName()); this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName().replace("org.apache", "com.alibaba")); this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName()); this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
(2)在Set中獲取到擴點類對應的key,通過getExtension獲取對應class的例項(包含通過setter進行依賴注入)
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<Object> holder = this.getOrCreateHolder(name); Object instance = holder.get(); if (instance == null) { synchronized(holder) { instance = holder.get(); if (instance == null) { //建立對應class的例項,完成依賴注入 instance = this.createExtension(name); holder.set(instance); } } } return instance; } }
createExtension方法是例項化的核心,實現了IOC和AOP,註釋如下:
private T createExtension(String name) { //獲取name對應的class Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { //例項化 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } //依賴注入(IOC) injectExtension(instance); //包裝器(AOP) Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } }
2.2 getAdaptiveExtension根據URL引數動態獲取相應的擴充套件點
public class AdaptiveMain { public static void main(String[] args) { URL url = URL.valueOf("test://localhost/hello?hello.service=dog"); HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension(); String msg = adaptiveExtension.sayHello(url); System.out.println(msg); } }
(1)核心程式碼為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; }
(2)建立自適應擴充套件類例項
private T createAdaptiveExtension() { try { //獲取自適應擴充套件類,並例項化,然後通過setter注入依賴 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
(3)生成自適應擴充套件類class
private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
(4)載入類擴充套件點(與上文相同)
private Map<String, Class<?>> getExtensionClasses() { Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
(5)建立自適應擴充套件class,動態生成程式碼,並進行編譯
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:
package com.exm.service; import org.apache.dubbo.common.extension.ExtensionLoader; public class HelloService$Adaptive implements com.exm.service.HelloService { public java.lang.String sayHello() { throw new UnsupportedOperationException("The method public abstract java.lang.String com.exm.service.HelloService.sayHello() of interface com.exm.service.HelloService is not adaptive method!"); } public java.lang.String sayHello(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("hello.service", "human"); if(extName == null) throw new IllegalStateException("Failed to get extension (com.exm.service.HelloService) name from url (" + url.toString() + ") use keys([hello.service])"); com.exm.service.HelloService extension = (com.exm.service.HelloService)ExtensionLoader.getExtensionLoader(com.exm.service.HelloService.class).getExtension(extName); return extension.sayHello(arg0); } }
可以看到自適應只支援具有adaptive註解的方法,並且引數彙總需要有URL引數。
(6)通過setter注入依賴
private T injectExtension(T instance) { if (objectFactory == null) { return instance; } try { for (Method method : instance.getClass().getMethods()) { if (!isSetter(method)) { continue; } /** * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) != null) { continue; } Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } try { String property = getSetterProperty(method); //獲取到Adaptive物件 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; }
(7)上文中的objectFactory是ExtensionFactory例項,其實現類包含SpiExtensionFactory和SpringExtensionFactory,一個是dubbo的擴充套件工廠,一個是Spring的工廠;前者只支援type是SPI的介面,並生成自適應類;後者從Spring容器中獲取。
在依賴注入時,會在兩個容器中遍歷,如下:
public <T> T getExtension(Class<T> type, String name) { for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; }
在getExtensionClasses()載入ExtensionFactory擴充套件class時,如果擴點類被Adaptive註解修飾,則將快取在ExtensionLoader.cachedAdaptiveClass中;
在getAdaptiveExtensionClass方法中,直接返回,不需要生成自適應類
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface."); } //判斷類事都是Adaptive類,是的話就快取,在getAdaptiveExtensionClass時直接返回 if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz); } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { clazz.getConstructor(); if (StringUtils.isEmpty(name)) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { cacheActivateClass(clazz, names[0]); for (String n : names) { cacheName(clazz, n); saveInExtensionClass(extensionClasses, clazz, n); } } } }
綜上,dubbo載入class擴充套件與例項化是分開的,可以通過指定key例項化某一個class;
dubbo支援IOC和AOP;
3.在注入依賴的時候是否有迴圈依賴的問題?
在dubbo建立擴充套件class例項時,會通過setter進行依賴注入,如果存在迴圈依賴,怎麼處理?
在dubbo依賴注入時,除了Spring容器外,從SPI容器中獲取,獲取的是SPI介面的自適應實現,是新建立的類,所以不存在迴圈依賴的問題。
牛逼的框架,就是讓你一眼看不懂它在幹什麼 ---me