dubbo的SPI應用與原理
dubbo
SPI(Service Provider Interface)
- 本質是 將介面實現類的全限定名配置在檔案中,並由服務載入器讀取配置檔案,載入實現類。這樣可以在執行時,動態為介面替換實現類。
- 在Java中SPI是被用來設計給服務提供商做外掛使用的。基於策略模式 來實現動態載入的機制 。我們在程式只定義一個介面,具體的實現交個不同的服務提供者;在程式啟動的時候,讀取配置檔案,由配置確定要呼叫哪一個實現;
- 透過 SPI 機制為我們的程式提供擴充功能,在dubbo中,基於 SPI,我們可以很容易的對 Dubbo 進行擴充。例如dubbo當中的protocol,LoadBalance等都是透過SPI機制擴充套件。
想要學習 Dubbo 的原始碼,SPI 機制務必弄懂。接下來,我們瞭解下JAVA SPI與dubbo SPI的用法,再分析DUBBO SPI的原始碼,本文的dubbo原始碼是基於2.7.5版本。
JAVA 原生SPI 示例
- 先簡單介紹JAVA SPI的應用。首先,我們定義一個Car介面
public interface Car { String getBrand(); }
- 定義該介面的兩個實現類。
public class BM implements Car { public String getBrand() { System.out.println("BM car"); return "BM"; } }public class Benz implements Car { public String getBrand() { System.out.println("benz car"); return "Benz"; } }
- 再在resources下建立META-INF/services 資料夾,並建立一個檔案,檔名稱為Car介面的全限定名com.dubbo.dp.spi.Car。內容為介面實現類的全限定類名。
com.dubbo.dp.spi.Benzcom.dubbo.dp.spi.BM
- 使用如下,呼叫Car介面在配置檔案中的所有實現類。
public class JavaSPITest { @Test public void sayHello() throws Exception { ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class); serviceLoader.forEach(car -> System.out.println(car.getBrand())); } }
JAVA SPI實現了介面的定義與具體業務實現解耦,應用程式可以根據實際業務情況啟用或替換具體元件。
舉例:JAVA的java.sql包中就定義一個介面Driver,各個服務提供商實現該介面。當我們需要使用某個資料庫時就匯入相應的jar包。
缺點
- 不能按需載入。Java SPI在載入擴充套件點的時候,會一次性載入所有可用的擴充套件點,很多是不需要的,會浪費系統資源;
- 獲取某個實現類的方式不夠靈活,只能透過 Iterator 形式獲取,不能根據某個引數來獲取對應的實現類。
- 不支援AOP與依賴注入。
- JAVA SPI可能會丟失載入擴充套件點異常資訊,導致追蹤問題很困難;
dubbo SPI示例
- dubbo重新實現了一套功能更強的 SPI 機制, 支援了AOP與依賴注入,並且 利用快取提高載入實現類的效能,同時 支援實現類的靈活獲取,文中接下來將講述SPI的應用與原理。
Dubbo的SPI介面都會使用@SPI註解標識,該註解的主要作用就是標記這個介面是一個SPI介面。原始碼如下:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public @interface SPI { /** * default extension name * 設定預設擴充類 */ String value() default ""; }
該註解只作用在介面上,value用來設定預設擴充類
- 首先講解下dubbo SPI的使用。在上述Car介面加上@SPI註解,它的實現類暫時不變,配置檔案的路徑與檔名也暫時不變,檔案內容調整如下:
@SPIpublic interface Car { String getBrand(); }
benz=com.dubbo.dp.spi.Benzbm=com.dubbo.dp.spi.BM
配置檔案透過鍵值對的方式進行配置,這樣我們可以按需載入指定的實現類。使用如下:
public class JavaSPITest { @Test public void sayHello() throws Exception { ExtensionLoader<Car> carExtensionLoader = ExtensionLoader.getExtensionLoader(Car.class); //按需獲取實現類物件 Car car = carExtensionLoader.getExtension("benz"); System.out.println(car.getBrand()); } }
輸出結果為
benz car Benz
Dubbo SPI 原始碼分析
在dubbo SPI示例方法中,我們首先透過
ExtensionLoader
的
getExtensionLoader
方法獲取一個介面的
ExtensionLoader
例項,然後再透過
ExtensionLoader
的
getExtension
方法獲取擴充類物件,原始碼如下,首先是
getExtensionLoader
方法:
/** * 擴充套件類載入器快取,就是擴充套件點ExtendsLoader例項快取; key=擴充套件介面 value=擴充套件類載入器 */ private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(); public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { //校驗傳進的type類是否為空 if (type == null) { throw new IllegalArgumentException("Extension type == null"); } //校驗傳進的type類是否為介面 if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } //校驗傳進的type類是否有@SPI註解 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } //從ExtensionLoader快取中查詢是否已經存在對應型別的ExtensionLoader例項 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { //沒有就new一個ExtensionLoader例項,並存入本地快取 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
getExtensionLoader
會對傳進的介面進行校驗,其中包括是否有
@SPI
註解校驗,這也是在介面上需加
@SPI
的原因。然後從
EXTENSION_LOADERS
快取中獲取該介面型別的
ExtensionLoader
,如果獲取不到,則建立一個該介面型別的
ExtensionLoader
放入到快取中,並返回該
ExtensionLoader
。
注意這裡建立
ExtensionLoader
物件的構造方法如下:ExtensionLoader.getExtensionLoader獲取ExtensionFactory介面的擴充類,再透過
getAdaptiveExtension
從擴充類中獲取目標擴充類。它會設定該介面對應的
objectFactory
常量為
AdaptiveExtensionFactory
。因為
AdaptiveExtensionFactory
類上加了@Adaptive註解,為什麼是
AdaptiveExtensionFactory
原因在之後的文章會解釋,且
objectFactory
也會在後面用到。
private ExtensionLoader(Class<?> type) { this.type = type; //type通常不為ExtensionFactory類,則objectFactory為ExtensionFactory介面的預設擴充套件類AdaptiveExtensionFactory objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
當透過
ExtensionLoader.getExtensionLoader
取到介面的載入器Loader之後,在透過
getExtension
方法獲取需要擴充類物件。該方法的整個執行流程如下圖所示
參照執行流程圖,擴充類物件的獲取原始碼如下:
/** * 擴充套件點例項快取 key=擴充套件點名稱,value=擴充套件例項的Holder例項 */ private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>(); /** * 獲取介面擴充類例項 * 1.檢查快取中是否存在 * 2.建立並返回擴充類例項 * @param name 需要獲取的配置檔案中擴充類的key * @return */ public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { // 獲取預設的擴充實現類,即@SPI註解上的預設實現類, 如@SPI("benz") return getDefaultExtension(); } // Holder,顧名思義,用於持有目標物件,從快取中拿,沒有則建立 final Holder<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); // 雙重檢查 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // 建立擴充例項 instance = createExtension(name); // 設定例項到 holder 中 holder.set(instance); } } } return (T) instance; } /** * 獲取或者建立一個Holder物件 */ private Holder<Object> getOrCreateHolder(String name) { // 首先透過副檔名從擴充套件例項快取中獲取Holder物件 Holder<Object> holder = cachedInstances.get(name); if (holder == null) { //如果沒有獲取到就new一個空的Holder例項存入快取 cachedInstances.putIfAbsent(name, new Holder<>()); holder = cachedInstances.get(name); } return holder; }
上面程式碼的邏輯比較簡單,首先檢查快取,快取未命中則建立擴充物件。dubbo中包含了大量的擴充套件點快取。這個就是典型的使用空間換時間的做法。也是Dubbo效能強勁的原因之一,包括
- 擴充套件點Class快取 ,Dubbo SPI在獲取擴充套件點時,會優先從快取中讀取,如果快取中不存在則載入配置檔案,根據配置將Class快取到記憶體中,並不會直接初始化。
- 擴充套件點例項快取 ,Dubbo不僅會快取Class,還會快取Class的例項。每次取例項的時候會優先從快取中取,取不到則從配置中載入,例項化並快取到記憶體中。
下面我們來看一下建立擴充物件的過程
/** * 擴充套件例項存入記憶體中快取起來; key=擴充套件類 ; value=擴充套件類例項 */ private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); /** * 建立擴充類例項,包含如下步驟 * 1. 透過 getExtensionClasses 獲取所有的擴充類,從配置檔案載入獲取擴充類的map對映 * 2. 透過反射建立擴充物件 * 3. 向擴充物件中注入依賴(IOC) * 4. 將擴充物件包裹在相應的 Wrapper 物件中(AOP) * @param name 需要獲取的配置檔案中擴充類的key * @return 擴充類例項 */ private T createExtension(String name) { // 從配置檔案中載入所有的擴充類,可得到“配置項名稱”到“配置類”的map,再根據擴充項名稱從map中取出相應的擴充類即可 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); } // 向例項中注入依賴,透過setter方法自動注入對應的屬性例項 injectExtension(instance); //從快取中取出所有的包裝類,形成包裝鏈 Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { // 迴圈建立 Wrapper 例項,形成Wrapper包裝鏈 for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } //初始化例項並返回 initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException("....."); } }
建立擴充類物件步驟分別為:
- 透過 getExtensionClasses 從配置檔案中載入所有的擴充類,再透過名稱獲取目標擴充類
- 透過反射建立擴充物件
- 向擴充物件中注入依賴
- 將擴充物件包裹在相應的 Wrapper 物件中
第三和第四個步驟是 Dubbo IOC 與 AOP 的具體實現。我們先重點分析getExtensionClasses 方法的邏輯
getExtensionClasses
方法的邏輯。
從配置檔案中載入所有的擴充類
-
在透過name獲取擴充類之前,首先需要根據配置檔案解析出擴充項名稱與擴充類的對映map,之後再根據擴充項名稱從map中取出相應的擴充類即可。
getExtensionClasses
方法原始碼如下/** * 擴充套件點Class快取 key=副檔名 ,value=對應的class物件 */ private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>(); /** * 解析配置檔案中介面的擴充項名稱與擴充類的對映表map * @return */ private Map<String, Class<?>> getExtensionClasses() { // 從快取中獲取已載入的擴充點class Map<String, Class<?>> classes = cachedClasses.get(); //雙重檢查 if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { // 載入擴充類 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
這裡也是先檢查快取,若快取未命中,則透過
loadExtensionClasses
載入擴充類,快取避免了多次讀取配置檔案的耗時。下面分析loadExtensionClasses
方法載入配置檔案的邏輯/** * 三個dubbo SPI預設掃描的路徑 */ private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/"; private Map<String, Class<?>> loadExtensionClasses() { //獲取並快取介面的@SPI註解上的預設實現類,@SPI("value")中的value cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); // 載入指定資料夾下的配置檔案,常量包含META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/三個資料夾 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true); //相容歷史版本 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
loadExtensionClasses
方法總共做了兩件事情。首先該方法呼叫cacheDefaultExtensionName
對 SPI 註解進行解析,獲取並快取介面的@SPI
註解上的預設擴充類在 cachedDefaultName。再呼叫loadDirectory
方法載入指定資料夾配置檔案。
SPI 註解解析過程比較簡單,原始碼如下。只允許一個預設擴充類。
private void cacheDefaultExtensionName() { // 獲取 SPI 註解,這裡的 type 變數是在呼叫 getExtensionLoader 方法時傳入,代表介面類 final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation == null) { return; } String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); // 檢測 SPI 註解內容是否合法(至多一個預設實現類),不合法則丟擲異常 if (names.length > 1) { throw new IllegalStateException("..."); } // 設定預設擴充類名稱 if (names.length == 1) { cachedDefaultName = names[0]; } } }
從原始碼中可以看出
loadExtensionClasses
方法載入配置檔案的路徑有3個,分別為
META-INF/dubbo/internal/
,
META-INF/dubbo/
,
META-INF/services/
三個資料夾。方法原始碼如下:
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) { loadDirectory(extensionClasses, dir, type, false); } /** * 載入配置檔案內容 * @param extensionClasses 擴充類map * @param dir 資料夾路徑 * @param type 介面名稱 * @param extensionLoaderClassLoaderFirst 是否先載入ExtensionLoader的ClassLoader */ private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) { // fileName = 資料夾路徑 + type 全限定名 String fileName = dir + type; try { Enumeration<java.net.URL> urls = null; //獲取當前執行緒的類載入器 ClassLoader classLoader = findClassLoader(); // try to load from ExtensionLoader's ClassLoader first if (extensionLoaderClassLoaderFirst) { //獲取載入ExtensionLoader.class這個類的類載入器 ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader(); //如果extensionLoaderClassLoaderFirst=true時,且這兩個類載入器不同,就優先使用 extensionLoaderClassLoader if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) { urls = extensionLoaderClassLoader.getResources(fileName); } } // 根據檔名載入所有的同名檔案 if(urls == null || !urls.hasMoreElements()) { if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); // 解析並載入配置檔案中配置的實現類到extensionClasses中去 loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { logger.error("").", t); } }
首先找到資料夾下的配置檔案,檔名需為介面全限定名。利用類載入器獲取檔案資源連結,再解析配置檔案中配置的實現類新增到
extensionClasses
中。我們繼續看loadResource是如何載入資源的。
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { String line; // 按行讀取配置內容 while ((line = reader.readLine()) != null) { final int ci = line.indexOf('#'); if (ci >= 0) { // 擷取 # 之前的字串,# 之後的內容為註釋,需要忽略 line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); if (i > 0) { // 以等於號 = 為界,擷取鍵與值 name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { // 透過反射載入類,並透過 loadClass 方法對類進行快取 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { ..... } } } } } catch (Throwable t) { logger.error(....); } }
loadResource
方法用於讀取和解析配置檔案,按行讀取配置檔案,每行以等於號 = 為界,擷取鍵與值,並透過反射載入類,最後透過
loadClass
方法載入擴充套件點實現類的class到map中,並對載入到的class進行分類快取。
loadClass
方法實現如下
/** * 載入擴充套件點實現類的class到map中,並對載入到的class進行分類快取 * 比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等 * @param extensionClasses 裝載配置檔案類的容器 * @param resourceURL 配置檔案資源URL * @param clazz 擴充套件點實現類的class * @param name 擴充套件點實現類的名稱,配置檔案一行中的key * @throws NoSuchMethodException */ private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { //判斷配置的實現類是否是實現了type介面 if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("..."); } //根據配置中實現類的型別來分類快取起來 // 檢測目標類上是否有 Adaptive 註解,表示這個類就是一個自適應實現類,快取到cachedAdaptiveClass if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz); // 檢測 clazz 是否是 Wrapper 型別,判斷依據是是否有引數為該介面類的構造方法,快取到cachedWrapperClasses } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { // 檢測 clazz 是否有預設的構造方法,如果沒有,則丟擲異常 clazz.getConstructor(); // 如果配置檔案中key的name 為空,則嘗試從Extension註解中獲取 name,或使用小寫的類名作為name。 // 已經棄用,就不在討論這種方式 if (StringUtils.isEmpty(name)) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("..."); } } //使用逗號將name分割為字串陣列 String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { //如果擴充套件點配置的實現類使用了@Activate註解,就將對應的註解資訊快取起來 cacheActivateClass(clazz, names[0]); for (String n : names) { //快取擴充套件點實現類class和擴充套件點名稱的對應關係 cacheName(clazz, n); //最後將class存入extensionClasses saveInExtensionClass(extensionClasses, clazz, n); } } } }
loadClass
方法實現了擴充套件點的分類快取功能,如包裝類,自適應擴充套件點實現類,普通擴充套件點實現類等分別進行快取。需要注意的是自適應擴充套件點實現類@Adaptive註解,該註解原始碼如下
*For example, given <code>String[] {"key1", "key2"}</code>: * <ol> * <li>find parameter 'key1' in URL, use its value as the extension's name</li> * <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li> * <li>use default extension if 'key2' doesn't exist either</li> * <li>otherwise, throw {@link IllegalStateException}</li> * @return */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; }
該註解的作用是決定哪個自適應擴充類被注入,該目標擴充類是由URL中的引數決定,URL中引數key由該註解的value給出,該key的value作為目標擴充類名稱。
- 如果註解中有多個值,則根據下標從小到大去URL中查詢有無對應的key,一旦找到就用該key的value作為目標擴充類名稱。
- 如果這些值在url中都沒有對應的key,使用spi上的預設值。
@Adaptive註解可以作用的類上與方法上, 絕大部分情況下,該註解是作用在方法上,當 Adaptive 註解在類上時,Dubbo 不會為該類生成代理類。註解在方法(介面方法)上時, Dubbo 則會為該方法生成代理類。 Adaptive 註解在介面方法上,表示擴充的載入邏輯需由框架自動生成。註解在類上,表示擴充的載入邏輯由人工編碼完成。
上述的
loadClass
掃描的是作用在類上。在 Dubbo 中,僅有兩個類被@Adaptive註解了,分別是
AdaptiveCompiler
和
AdaptiveExtensionFactory
。
loadClass
方法設定快取
cacheAdaptiveClass
會導致介面的
cacheAdaptiveClass
不為空,後面都會預設用這個擴充類,優先順序最高。
回到主線,當執行完
loadClass
方法,配置檔案中的所有擴充類已經被載入到map中,到此,關於快取類載入的過程就分析完了。
Dubbo IOC
當
getExtensionClasses()
方法執行流程完成後,再根據擴充項name從map中取出相應的擴充類即可獲取擴充套件類Class,透過反射建立例項,並透過
injectExtension(instance);
方法向例項中注入依賴
這一部分在下一篇文章中將會介紹。
DUBBO AOP
當執行完
injectExtension(T instance)
方法,在
createExtension(String name)
就開始執行
wrapper
的包裝,類似於spring中的AOP,dubbo運用了裝飾器模式。
Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { // 迴圈建立 Wrapper 例項,形成Wrapper包裝鏈 for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } }
這裡的
cachedWrapperClasses
透過前面的分析已經知道,就是在解析配置檔案時判斷是否是
Wrapper
型別的擴充類,++判斷依據為構造方法中是否有引數為該介面類++,則快取到cachedWrapperClasses。
執行
wrapperClass.getConstructor(type).newInstance(instance)
將獲取包裝類的構造方法,方法的引數就是該介面型別,並透過反射生成一個包含該擴充類例項的包裝物件,再透過
injectExtension
注入包裝物件的依賴。如此迴圈,得到成Wrapper包裝鏈。這裡需注意的是,
配置檔案中內容靠後的包裝類會包裝在相對外層。下面是DUBBO AOP的例子,我們繼續使用上面的Car介面與實現類,同時新增一個實現類,程式碼如下
public class CarWrapper implements Car{ private Car car; /** * 有一個包含該介面類引數的構造方法 */ public CarWrapper(Car car) { this.car = car; } @Override public String getBrand() { System.out.println("校驗"); String result = car.getBrand(); System.out.println("記錄日誌"); return result; } }
該介面實現了Car,並且持有一個Car物件,同時擁有一個構造方法且該構造方法的引數為Car介面型別,那麼該類會被識別為介面的Wrapper類。則可以在方法中做一些切面功能的擴充套件,再呼叫car物件執行其方法實現AOP功能。
將配置檔案內容中新增wrapper實現類,如下
benz=com.xiaoju.automarket.energy.scm.rpc.Benz bm=com.xiaoju.automarket.energy.scm.rpc.BM com.xiaoju.automarket.energy.scm.rpc.CarWrapper #包裝類
執行如下程式碼獲取benz的擴充類例項後,呼叫其方法,將會被Wrapper包裝類
public class TestAOP { public static void main(String[] args) { ExtensionLoader<Car> carExtensionLoader = ExtensionLoader.getExtensionLoader(Car.class); Car car = carExtensionLoader.getExtension("benz"); System.out.println(car.getBrand(null)); } }
結果如下
校驗 benz car 記錄日誌 Benz
與我們預想的一致,實現了Wrapper類的切面功能。
總結
本篇簡單分別介紹了 Java SPI 與 Dubbo SPI 用法,並對 Dubbo SPI 的載入擴充類的過程進行了分析。同時分析了Dubbo AOP的實現原理。如果文章中有錯誤不妥之處,希望大家指正。
下一篇文章將講述 Dubbo SPI 的擴充套件點自適應機制,dubbo自動注入中也涉及到了該部分,即SpringExtensionFactory執行factory.getExtension涉及到載入自適應擴充點。
作者:丁鵬
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559758/viewspace-2678246/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java SPI 與 Dubbo SPIJava
- Dubbo2.7的Dubbo SPI實現原理細節
- Dubbo(一)-SPI(2) 機制之 Dubbo 的 SPI
- dubbo之SPI
- 溫習 SPI 機制 (Java SPI 、Spring SPI、Dubbo SPI)JavaSpring
- Java RPC原理及Dubbo的實踐應用JavaRPC
- 棧的原理與應用
- SSH 的原理與應用
- Dubbo原始碼分析(三)Dubbo中的SPI和自適應擴充套件機制原始碼套件
- SPI 原理
- Java Spi是如何找到你的實現的? ——Java SPI原理與實踐Java
- Dubbo剖析-增強SPI的實現
- Dubbo原始碼解析之SPI原始碼
- dubbo原始碼解析-spi(五)原始碼
- dubbo SPI功能解析(一)
- Dubbo之SPI原始碼分析原始碼
- influxdb 原理與應用UX
- 剖析 SPI 在 Spring 中的應用Spring
- SPI介面在LCD上的應用
- Dubbo底層原理分析和分散式實際應用分散式
- Dubbo 原始碼分析 - SPI 機制原始碼
- Dubbo和JDK的SPI究竟有何區別?JDK
- 淺析Dubbo的SPI擴充套件機制套件
- 由淺入深理解Dubbo的SPI機制
- 詳解Apache Dubbo的SPI實現機制Apache
- Dubbo原始碼解析之SPI機制原始碼
- Dubbo系列之 (一)SPI擴充套件套件
- 搞懂Dubbo SPI可擴充機制
- Dubbo原始碼學習之-SPI介紹原始碼
- Dubbo(一)-SPI 機制之javaSPI基礎Java
- API介面:原理、設計與應用API
- 從Dubbo核心-SPI聊聊雙親委派機制
- Dubbo SPI 原始碼學習 & admin安裝(二)原始碼
- 聊聊Dubbo(五):核心原始碼-SPI擴充套件原始碼套件
- 企業級SpringBoot與Dubbo的並用Spring Boot
- SPI與APIAPI
- 大資料技術原理與應用大資料
- 強化學習之原理與應用強化學習