dubbo最核心的思想就是就是SPI服務發現,可以實現動態加載實現類。http://dubbo.apache.org/zh-cn/docs/dev/SPI.html 在dubbo的官方文檔中用擴展點的介紹。我們可以看到,dubbo擴展點主要有四大特性
- 擴展點自動包裝。
- 擴展點自動裝配.
- 擴展點自適應.
- 擴展點自動激活
接下來我會以Dubbo的Protocol 接口爲例來說明一下dubbo的四大特性。
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
我們首先看第一條,擴展點自動包裝,自動包裝擴展點的Wrapper類。ExtensionLoader在加載擴展點時,如果加載到的擴展點有拷貝構造函數,則判定爲擴展點Wrapper類。以Protocol爲例。我們可以看一下Protocol的繼承關係。
我們可以看到,其中有兩個是以Wrapper結尾,點進去看一下這兩個類,我們可以看到,他們都是有一個構造函數,參數是Protocol,也就是拷貝構造函數。那麼,當dubbo掃描到這兩個類時,就會把他們當作Protocol的包裝類。
接下來再看官方文檔,下面有介紹:“Wrapper 類同樣實現了擴展點接口,但是 Wrapper 不是擴展點的真正實現。它的用途主要是用於從 ExtensionLoader 返回擴展點時,包裝在真正的擴展點實現外。即從 ExtensionLoader 中返回的實際上是 Wrapper 類的實例,Wrapper 持有了實際的擴展點實現類。”
也就是我們拿到的擴展點實例其實都是經過了Wrapper包裝。
接下來我們可以再去源碼看看他是怎麼實現的。我們看一下Dubbo ExtensionLoader中的源碼
private T createExtension(String name) {
// 通過名字獲取具體的類
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// 從緩存中根據class獲取對象
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 通過反射新建一個對象
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
// 重點來了,這裏會拿到dubbo掃描的wrapper類,並通過循環
// 調用每一個wrapper類的構造參數爲擴展點類的構造函數,
// 所以我們最後拿到的實例是經過了一層又一層的包裝後的實例,
// 有點類似於aop
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
接下來再看一下dubbo是如何尋找wrapper類的,繼續看源碼
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
..............,
省略讀配置文件的代碼
.................
// 此處是關鍵,我們通過讀配置文件拿到了class類,通過反射調用,判斷
// 是否有拷貝構造函數,有的話說明該類是包裝類,就會加入到緩存中,
// 沒有就會執行catch中的代碼
try {
clazz.getConstructor(type);
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
clazz.getConstructor();
接下來的兩大特性,在下次會繼續介紹