探究Dubbo的拓展機制: 下
承接上篇, 本篇博文的主題就是認認真真捋一捋, 看一下 Dubbo是如何實現他的IOC / AOP / 以及Dubbo SPI這個拓展點的
總覽:
本篇的話總體上分成兩部分進行展開
- 第一點就是 Dubbo在啟動過程中載入原生的配置檔案中提供的被@SPI標記的實現類:
- 第二就是Dubbo載入程式設計師後續新增進去的被@SPI標註的介面和實現類, 進而探究 Dubbo的IOC / AOP / 以及Dubbo SPI這個拓展點機制
環境的初始化
入口程式
如下程式碼是追蹤的起點:
我也是看了好多遍才勉強將這個過程整理明白一些, 但是根據以往的經驗來說, 過一倆月之後我可能就會淡忘這個流程... 為了讓自己一段時間後快速的回憶起來這個流程, 所以我要對自己說下面一段話
Dubbo的拓展點編碼實現中, 會反反覆覆的出現下面這段程式碼
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(XXX.class); extensionLoader.getExtension("XXX");
先說這段程式碼在幹什麼? 其實上它就是在為 Dubbo原生的SPI介面, 或者是使用者提供的SPI介面 結合SPI的配置檔案中的配置, 找到這些SPI介面的實現類, 並且這個過程中穿插Dubbo的IOC已經AOP機制
不得不服氣, 這一段程式碼的實現, 因為這段程式碼設計不僅僅能載入Dubbo提供的原生的SPI介面, 也能載入使用 使用者自定義的SPI , 詳細的過程在下文中展開, 妙!!!
明星類 ExtensionLoader.java
應該得, 隆重的介紹一下這個明星類 ExtensionLoader.java
從名字上看, ExtensionLoader , 見名知意, 拓展點的載入器, 那什麼是Dubbo的拓展點呢? 拓展點就是Dubbo允許使用者參與到Dubbo環境的初始化這個過程中來, 允許使用者定製Dubbo行為, 諸如 Dubbo的 SPI / IOC / AOP (上一篇博文主要的學習內容就是Dubbo的拓展點的使用)
見名知意: ExtensionLoader 拓展點的載入器, 就是使用這個封裝類, 我們可以載入Dubbo提供的拓展點, 說白了, 其實載入為SPI介面找到實現類, 以及完成這些實現類之間的 AOP增強 + IOC 依賴注入的過程
此外這個類很有必要看, 為啥呢? 第一點就是說它的設計很巧妙, 程式碼的抽象和複用能力都很好, 第二點就是說, 我們可以一睹大神們的風采, 如果 實現自己的SPI , 如何實現自己的IOC AOP
入口方法
下面就是入口程式中的第一個方法, getExtensionLoader(Class<T> type)
很簡單, 就是根據型別找到對應的 ExtensionLoader, 待會Dubbo就會為我新增進去SPI介面生成這樣的 ExtensionLoader : org.apache.dubbo.common.extension.ExtensionLoader[com.changwu.ioc.api.PersonInterface]
當然Dubbo也有自己原生的ExtensionLoader
從我的入口程式來看, 很顯然, 我傳遞進來的 type = PersonInterface
, 方法執行的邏輯如下
- 對type引數進行校驗
- 檢查快取中是否存在 PersonInterface的 ExtensionLoader
- 如果有的話, 返回這個現存的ExtensionLoader
- 如果不存在就建立一個新的
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!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
//todo 這裡體現了快取機制, EXTENSION_LOADERS 其實就是 CurrentHashMap
//todo EXTENSION_LOADERS 是 CurrentHashMap , 每一種interfaceType 都對應一個 ExtensionLoader , 但是這些 ExtensionLoader全部被維護在 這個EXTENSION_LOADER中
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蹊蹺的構造方法
那我們是第一次進來, 肯定是沒有的, 因此我們看他是如何進行new ExtensionLoader<T>(type)
的, 所以跟進看一下它的構造方法
private ExtensionLoader(Class<?> type) {
this.type = type;
// 對於一個介面,比如PersonInterface介面,有兩種實現類,一種就是我們自定義的實現類,比如Student,還有一種就是代理類,對於代理類,可以由我們自己實現,也可以讓Dubbo幫我們實現,而代理類主要就是依賴注入時使用
// todo ExtensionFactory 是dubbo的拓展機制工廠, 它裡面封裝了 Dubbo的SPI拓展機制和Spring的拓展機制
// todo ExtensionLoader.getExtensionLoader(ExtensionFactory.class) ===> 獲取 自適應的extension
// || || || || || ||
// todo ExtensionLoader.getExtensionLoader(PersonInterface.class) ===> 昌武, 你獲取的是: extension("human")
// todo 你看這是不是挺清晰的
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
在上面的構造方法中, 就有蹊蹺了, 邏輯如下
- 將type = PersonInterface 儲存起來
- 獲取 ExtensionFactory.class 型別的 ExtensionLoader
- 獲取 ExtensionFactory.class 型別的 ExtensionLoader的自適應的 Extension
我所說的有蹊蹺的地方: 我們本來不是前來建立PersionInterface 的ExtensionLoader嗎? 怎麼先建立 ExtensionFactory的 ExtensionLoader呢?
(因為在建立ExtensionFactory的 ExtensionLoader的過程中會去載入Dubbo提供的其他的諸如SpiExtensionFactory這一類的實現, 這些預設的實現的作用就是輔助Dubbo再去解析使用者提供的SPI實現體系)
下面看看這個 ExtensionFactory.class類
沒錯! 它被新增上了@SPI的註解, 說明和 我們的PersonInterface一樣, 是DubboSPI
那好吧, 既然Dubbo想先完成它的例項化, 就往下看, 我在博文開頭就不停的說, Dubbo設計的很好, 這裡不就遞迴呼叫getExtensionLoader(type= ExtensionFactory.class)了嗎? 不出意外的話, 再一次的 進去構造方法, 然後在這個三元判斷表示式中發現了 type == ExtensionFactory.class ? null : ExtensionLoader.getE... 前半部分是滿足條件的, 然後設定objectFactory=null, 完成 ExtensionFactory的構造, 然後執行getAdaptiveExtension()
獲取自適應的Extension
這個 getAdaptiveExtension()
同樣需要好好的看看, 見名知意, 返回一個自適應的 Extension, 說白了就是返回Dubbo通過字元拼接出來的Extension類
下面看看這個 getAdaptiveExtension()
原始碼如下:
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
// todo 為了執行緒安全 , 使用了雙重同步鎖
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// todo 跟進去
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
著重跟進 instance = createAdaptiveExtension();
方法, 原始碼如下: 主要邏輯如下:
- 獲取出 AdaptiveExtensionClass
- 啟動過程中, 第一次獲取到的是 Dubbo提供的預設實現類, 叫 AdaptiveExtensionFactory
- 第二次獲取到的是 Dubbo為使用者提供的SPI介面動態生成的實現類
- 例項化AdaptiveExtensionClass
- 對例項化的AdaptiveExtensionClass 進行依賴注入的操作
載入SPI配置檔案, 獲取所有的ExtensionClass
ExtensionClass 可以直白的理解成 SPI 介面的實現類, 或者是wrapper類
上面的程式碼中想要獲取一個 AdaptiveExtensionClass()
那麼問題來了, 從哪裡獲取呢? 跟進getAdaptiveExtensionClass()
沒錯就在下面的
private Class<?> getAdaptiveExtensionClass() {
// todo 載入配置檔案
getExtensionClasses();
// todo 在前一步載入配置檔案時, 載入到了 AdaptiveExtensionFactory, 這裡返回的就是 CachedAdaptiveClass
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 如果沒有手動實現介面的代理類,那麼Dubbo就會自動給你實現一個
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
往下跟進getExtensionClasses();
下面的函式中維護著一個 cachedClasses
它是一個Map , key=String value= Class ; 說白了, 存放的就是從SPI配置檔案中讀取配置資訊
// 實際上就是將配置中的 key=value 讀取裝在進map中
private Map<String, Class<?>> getExtensionClasses() {
// todo cachedClasses是 ExtensionLoader的屬性: Holder<Map<String, Class<?>>> cachedClasses
// todo 用於儲存提前約定好了儲存在 類路徑下的 METE-INF/services 以及dubbo原生提供的擴充套件點
// todo 同樣是雙重同步鎖 + volatile 保證執行緒安全
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// todo 著重看這個函式
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
進行跟進loadExtensionClasses();
可以看到, Dubbo會按照約定讀取下面幾個配置檔案中的配置資訊, 下面我註釋上的檔案的全路徑所對應的檔案中會記錄Dubbo原生的SPI的實現, 我們也能遵循這個規則提供自己的實現類
比如隨便檢視一個配置檔案
// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// todo 跟進這個 loadDirectory() 方法, 看看
// todo META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
// todo META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
// todo META-INF/dubbo/org.apache.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
//todo META-INF/dubbo/com.alibaba.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//todo META-INF/services/org.apache.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
//todo META-INF/services/com.alibaba.dubbo.common.extension.ExtensionFactory
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
下面看一下處理的詳細細節資訊:
- 如果從配置檔案中讀取到的 SPI的實現類添加了@Adaptive註解, 就先快取起來
- Dubbo將
AdaptiveExtensionFactory.java
暫時快取起來了
- Dubbo將
- 沒新增@Adaptive的話, 同樣將其快取在不同額容器中, 稍後使用
- Dubbo建立的例項物件是:
SpiExtensionFactory,java
- Dubbo建立的例項物件是:
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.");
}
// todo 檢視有沒有標註 @Adaptive 註解, 如果標註有這個註解的話, 那麼就將他暫時存放起來, 而不是執行下面的邏輯, 構造出物件來
// todo 昌武, 你看, 你在驗證ioc時, 你提供的PersonInterface很顯然是存在這個@Adaptive註解 ,他會在上面提到getAdaptiveClasss() 後 然後newInstance()建立例項
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, name);
}
}
}
}
小結: 到這裡基本上就到了Dubbo的底層確實會去讀取配置檔案, 根據他們的配置情況, 快取在不同容器中
好, 到這裡上面所說的getExtensionClasses();
方法就說完了, 回到下面的方法中
得到了AdaptiveExtensionFactory
類之後, 接著就通過反射建立的它例項物件, 所以說, 我們要去看他的構造方法, 如下:
- AdaptiveExtensionFactory繼承了 ExtensionFactory, 因此它需要重寫
getExtension(Class<T> type, String name)
- 重點看他的構造方法
又看到了這行程式碼ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
這行程式碼的執行流程其實已經說過了, 這次根據名稱獲取的 Extension是 SpiExtensionFactory, 並將它維護起來
新的問題就來了, 這個SPIExtensionFactory是誰呢? 有啥用呢 看下面, 說白了, 用它處理新增有Dubbo的SPI註解的介面, 然後嘗試獲取這些介面的 實現
構建方法執行完成了, 也就說明 AdaptiveExtension 建立完成了, 剛才所說的 createAdaptiveExtension
injectExtension其實就是回去做IOC / AOP 相關的操作, 現在我們跟蹤的實現類是 AdaptiveExtension
它沒有依賴其他的屬性, 但是我提供的PersonInterface依賴了, 所以說我們暫時先不進如這個方法,稍後再進去檢視他的實現
小結: 下圖是我們的啟動類, 到目前為止, 我們就看完了啟動類的第一行程式碼做了什麼, 那它主要是做了哪些事情呢?
- 例項化 : AdaptiveExtensionFactory
- 例項化 : SPIExtensionFactory
- 可用來處理使用者後續新增進來的SPI相關邏輯
- 例項化 : 使用者提供的Spi介面的 ExtensionLoader
Dubbo的IOC細節
下面就繼續看這行extensionLoader.getExtension("human")
, 看他的返回值, 很明顯, 就是要返回我們需要的personInterface的實現類, 並在這個過程中穿插這IOC和AOP的邏輯
回顧一下實驗的環境, 重新整理一下思路: 我們想獲取出 key = human的 PersonInterface的實現類, 這實現類長下面這樣:
public class Human implements PersonInterface {
private PersonInterface personInterface;
//todo 第一個關注點: 我們的關注點就是說, Human 會幫我們將哪一個實現類當成入參注入進來呢?
//todo 答案是 URL ,dubbo自己封裝的URL, 統一資源定位符, dubbo 會解析入參位置的 url中封裝的map
//todo map中的key 與 PersonInteface中的使用 @Adaptive("person") 註解標記的value對應, 那麼值就是將要注入的實際型別
//todo 第二個關注點: dubbo底層很可能是通過反射使用構造方法完成的屬性注入
public void setPersonInterface(PersonInterface personInterface) {
this.personInterface = personInterface;
}
@Override
public String getName(URL url) {
System.out.println("i am Human ");
return "i am Human + " + personInterface.getName(url);
}
}
可以很直接的看到, 這個實現類其實是依賴了一個 PersionInterface的屬性,需要將這個屬性注入給他, 於是問題來了, 注入的是誰呢? 下面繼續往下拉看
進入下面的方法, 主要邏輯如下
- 根據那麼取出ExtensionClasses的 Class 物件,
- 這獲取出來的物件就是我們前面所說的,就是讀取SPI配置檔案時獲取出來的物件
- 呼叫injectExtension()方法, 完成物件依賴屬性的注入
- 實現Dubbo的AOP , 完成物件方法切面的增強
我們先看下: injectExtension(instance)的實現細節:
主要邏輯如下:
- 通過反射獲取出物件的所有的方法
- 如果不是setter方法就返回 (體現出, Dubbo的依賴注入是藉助setter方法實現的)
- 如果新增的@Disable註解, 表示明確指定不會進行注入
- 嘗試獲取出當前物件所依賴的物件, 也就是下面的
objectFactory.getExtension(pt,property)
- 其中objectFactory就是前面創建出來的SPIExtensionFactory
- pt=PersonInterface的Class 描述物件
- property 是從 setPersonInterface()方法中截取出來的: personInterface 字串
上圖中的主要目的就是完成依賴注入, 什麼依賴注入呢? 就是在 Human.java中 依賴了一個PersonInterface型別的屬性, Dubbo需要幫我填充上 , HumanInterface.java 中鎖依賴的那個具體的實現類是誰呢? 就是在上面函式中的通過 objectFactory.getExtension(Class,name)
動態生成出來的
當我們繼續跟進這個getExtension(), 就會發現下面的現象, 看我在下圖中標出來的綠色部分, 可以發現 , 他獲取出來的 ExtensionLoader全稱如下: 它就是Dubbo我們生成出來的代理 ExtensionLoader
再進一步, 通過loader 獲取出自適應的拓展類: getAdapativeExtension()
通過反編譯看一下生成的Interface是誰, 可以看一下,它的實現, 這就是為什麼Dubbo通過URL就能知道該注入誰, 用誰取幹活
Dubbo的AOP細節 (wrapper)
先說啥是AOP, 就是面向切面程式設計, 其實說白了就是對現有的物件進行增強
Dubbo是怎麼做的呢? 按照Dubbo的約定, 我們的這樣編碼:即 通過繼承+構造方法 實現AOP , 就像下面這樣
Dubbo的底層實現: 處理AOP的邏輯在下面
Dubbo會從SPI配置檔案中找到我們新增就進去的Wrapperlei, 通過構造方法反射出他們的例項,, 重要的是將反射出來的這個例項替換成了原來未被增強的 物件, 就跟java的感覺就像是升級版的靜態代理一樣
最後打一個小廣告: 我是bloger 賜我白日夢, 本科大三在讀, 熱衷java研發, 期望有一份Java相關實習崗位的工作, 可以全職實習半年左右, 最理想城市是北京, 求大佬的內推哇