摘要: 在Dubbo可擴充套件機制實戰中,我們瞭解了Dubbo擴充套件機制的一些概念,初探了Dubbo中LoadBalance的實現,並自己實現了一個LoadBalance。是不是覺得Dubbo的擴充套件機制很不錯呀,接下來,我們就深入Dubbo的原始碼,一睹廬山真面目。
在Dubbo可擴充套件機制實戰中,我們瞭解了Dubbo擴充套件機制的一些概念,初探了Dubbo中LoadBalance的實現,並自己實現了一個LoadBalance。是不是覺得Dubbo的擴充套件機制很不錯呀,接下來,我們就深入Dubbo的原始碼,一睹廬山真面目。
ExtensionLoader
ExtentionLoader是最核心的類,負責擴充套件點的載入和生命週期管理。我們就以這個類開始吧。
Extension的方法比較多,比較常用的方法有:
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)
public T getExtension(String name)
public T getAdaptiveExtension()
比較常見的用法有:
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName)
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension()
說明:在接下來展示的原始碼中,我會將無關的程式碼(比如日誌,異常捕獲等)去掉,方便大家閱讀和理解。
*1. getExtensionLoader方法
這是一個靜態工廠方法,入參是一個可擴充套件的介面,返回一個該介面的ExtensionLoader實體類。通過這個實體類,可以根據name獲得具體的擴充套件,也可以獲得一個自適應擴充套件。
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// 擴充套件點必須是介面
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
// 必須要有@SPI註解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type without @SPI Annotation!");
}
// 從快取中根據介面獲取對應的ExtensionLoader
// 每個擴充套件只會被載入一次
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;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
*2. getExtension方法
public T getExtension(String name) {
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
// 從快取中獲取,如果不存在就建立
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
getExtention方法中做了一些判斷和快取,主要的邏輯在createExtension方法中。我們繼續看createExtention方法。
private T createExtension(String name) {
// 根據擴充套件點名稱得到擴充套件類,比如對於LoadBalance,根據random得到RandomLoadBalance類
Class<?> clazz = getExtensionClasses().get(name);
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 使用反射呼叫nesInstance來建立擴充套件類的一個示例
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 對擴充套件類示例進行依賴注入
injectExtension(instance);
// 如果有wrapper,新增wrapper
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
}
createExtension方法做了以下事情:
*1. 先根據name來得到對應的擴充套件類。從ClassPath下META-INF資料夾下讀取擴充套件點配置檔案。
*2. 使用反射建立一個擴充套件類的例項
*3. 對擴充套件類例項的屬性進行依賴注入,即IoC。
*4. 如果有wrapper,新增wrapper,即AoP。
下面我們來重點看下這4個過程
*1. 根據name獲取對應的擴充套件類
先看程式碼:
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;
}
// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if (value != null && (value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName());
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
過程很簡單,先從快取中獲取,如果沒有,就從配置檔案中載入。配置檔案的路徑就是之前提到的:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
*2. 使用反射建立擴充套件例項
這個過程很簡單,使用clazz.newInstance())來完成。建立的擴充套件例項的屬性都是空值。
*3. 擴充套件例項自動裝配
在實際的場景中,類之間都是有依賴的。擴充套件例項中也會引用一些依賴,比如簡單的Java類,另一個Dubbo的擴充套件或一個Spring Bean等。依賴的情況很複雜,Dubbo的處理也相對複雜些。我們稍後會有專門的章節對其進行說明,現在,我們只需要知道,Dubbo可以正確的注入擴充套件點中的普通依賴,Dubbo擴充套件依賴或Spring依賴等。
*4. 擴充套件例項自動包裝
自動包裝就是要實現類似於Spring的AOP功能。Dubbo利用它在內部實現一些通用的功能,比如日誌,監控等。關於擴充套件例項自動包裝的內容,也會在後面單獨講解。
經過上面的4步,Dubbo就建立並初始化了一個擴充套件例項。這個例項的依賴被注入了,也根據需要被包裝了。到此為止,這個擴充套件例項就可以被使用了。
Dubbo SPI高階用法之自動裝配
自動裝配的相關程式碼在injectExtension方法中:
private T injectExtension(T instance) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
}
}
return instance;
}
要實現對擴充套件例項的依賴的自動裝配,首先需要知道有哪些依賴,這些依賴的型別是什麼。Dubbo的方案是查詢Java標準的setter方法。即方法名以set開始,只有一個引數。如果擴充套件類中有這樣的set方法,Dubbo會對其進行依賴注入,類似於Spring的set方法注入。
但是Dubbo中的依賴注入比Spring要複雜,因為Spring注入的都是Spring bean,都是由Spring容器來管理的。而Dubbo的依賴注入中,需要注入的可能是另一個Dubbo的擴充套件,也可能是一個Spring Bean,或是Google guice的元件,或其他任何一個框架中的元件。Dubbo需要能夠從任何一個場景中載入擴充套件。在injectExtension方法中,是用Object object = objectFactory.getExtension(pt, property)來實現的。objectFactory是ExtensionFactory型別的,在建立ExtensionLoader時被初始化:
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
objectFacory本身也是一個擴充套件,通過ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())來獲取。
ExtensionLoader有三個實現:
*1. SpiExtensionLoader:Dubbo自己的Spi去載入Extension
*2. SpringExtensionLoader:從Spring容器中去載入Extension
*3. AdaptiveExtensionLoader: 自適應的AdaptiveExtensionLoader
這裡要注意AdaptiveExtensionLoader,原始碼如下:
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
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;
}
}
AdaptiveExtensionLoader類有@Adaptive註解。前面提到了,Dubbo會為每一個擴充套件建立一個自適應例項。如果擴充套件類上有@Adaptive,會使用該類作為自適應類。如果沒有,Dubbo會為我們建立一個。所以ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())會返回一個AdaptiveExtensionLoader例項,作為自適應擴充套件例項。
AdaptiveExtentionLoader會遍歷所有的ExtensionFactory實現,嘗試著去載入擴充套件。如果找到了,返回。如果沒有,在下一個ExtensionFactory中繼續找。Dubbo內建了兩個ExtensionFactory,分別從Dubbo自身的擴充套件機制和Spring容器中去尋找。由於ExtensionFactory本身也是一個擴充套件點,我們可以實現自己的ExtensionFactory,讓Dubbo的自動裝配支援我們自定義的元件。比如,我們在專案中使用了Google的guice這個IoC容器。我們可以實現自己的GuiceExtensionFactory,讓Dubbo支援從guice容器中載入擴充套件。
Dubbo SPI高階用法之AoP
在用Spring的時候,我們經常會用到AOP功能。在目標類的方法前後插入其他邏輯。比如通常使用Spring AOP來實現日誌,監控和鑑權等功能。
Dubbo的擴充套件機制,是否也支援類似的功能呢?答案是yes。在Dubbo中,有一種特殊的類,被稱為Wrapper類。通過裝飾者模式,使用包裝類包裝原始的擴充套件點例項。在原始擴充套件點實現前後插入其他邏輯,實現AOP功能。
什麼是Wrapper類
那什麼樣類的才是Dubbo擴充套件機制中的Wrapper類呢?Wrapper類是一個有複製建構函式的類,也是典型的裝飾者模式。下面就是一個Wrapper類:
class A{
private A a;
public A(A a){
this.a = a;
}
}
類A有一個建構函式public A(A a),建構函式的引數是A本身。這樣的類就可以成為Dubbo擴充套件機制中的一個Wrapper類。Dubbo中這樣的Wrapper類有ProtocolFilterWrapper, ProtocolListenerWrapper等, 大家可以檢視原始碼加深理解。
怎麼配置Wrapper類
在Dubbo中Wrapper類也是一個擴充套件點,和其他的擴充套件點一樣,也是在META-INF資料夾中配置的。比如前面舉例的ProtocolFilterWrapper和ProtocolListenerWrapper就是在路徑dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中配置的:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
在Dubbo載入擴充套件配置檔案時,有一段如下的程式碼:
try {
clazz.getConstructor(type);
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {}
這段程式碼的意思是,如果擴充套件類有複製建構函式,就把該類存起來,供以後使用。有複製建構函式的類就是Wrapper類。通過clazz.getConstructor(type)來獲取引數是擴充套件點介面的建構函式。注意建構函式的引數型別是擴充套件點介面,而不是擴充套件類。
以Protocol為例。配置檔案dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中定義了filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper。
ProtocolFilterWrapper程式碼如下:
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
// 有一個引數是Protocol的複製建構函式
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
ProtocolFilterWrapper有一個建構函式public ProtocolFilterWrapper(Protocol protocol),引數是擴充套件點Protocol,所以它是一個Dubbo擴充套件機制中的Wrapper類。ExtensionLoader會把它快取起來,供以後建立Extension例項的時候,使用這些包裝類依次包裝原始擴充套件點。
擴充套件點自適應
前面講到過,Dubbo需要在執行時根據方法引數來決定該使用哪個擴充套件,所以有了擴充套件點自適應例項。其實是一個擴充套件點的代理,將擴充套件的選擇從Dubbo啟動時,延遲到RPC呼叫時。Dubbo中每一個擴充套件點都有一個自適應類,如果沒有顯式提供,Dubbo會自動為我們建立一個,預設使用Javaassist。
先來看下建立自適應擴充套件類的程式碼:
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
}
}
}
return (T) instance;
}
繼續看createAdaptiveExtension方法
private T createAdaptiveExtension() {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
}
繼續看getAdaptiveExtensionClass方法
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
繼續看createAdaptiveExtensionClass方法,繞了一大圈,終於來到了具體的實現了。看這個createAdaptiveExtensionClass方法,它首先會生成自適應類的Java原始碼,然後再將原始碼編譯成Java的位元組碼,載入到JVM中。
private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
Compiler的程式碼,預設實現是javassist。
@SPI(“javassist”)
public interface Compiler {
Class<?> compile(String code, ClassLoader classLoader);
}
createAdaptiveExtensionClassCode()方法中使用一個StringBuilder來構建自適應類的Java原始碼。方法實現比較長,這裡就不貼程式碼了。這種生成位元組碼的方式也挺有意思的,先生成Java原始碼,然後編譯,載入到jvm中。通過這種方式,可以更好的控制生成的Java類。而且這樣也不用care各個位元組碼生成框架的api等。因為xxx.java檔案是Java通用的,也是我們最熟悉的。只是程式碼的可讀性不強,需要一點一點構建xx.java的內容。
下面是使用createAdaptiveExtensionClassCode方法為Protocol建立的自適應類的Java程式碼範例:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
大致的邏輯和開始說的一樣,通過url解析出引數,解析的邏輯由@Adaptive的value引數控制,然後再根據得到的擴充套件點名獲取擴充套件點實現,然後進行呼叫。如果大家想知道具體的構建.java程式碼的邏輯,可以看createAdaptiveExtensionClassCode的完整實現。
在生成的Protocol$Adpative中,發現getDefaultPort和destroy方法都是直接丟擲異常的,這是為什麼呢?來看看Protocol的原始碼:
@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();
可以看到Protocol介面中有4個方法,但只有export和refer兩個方法使用了@Adaptive註解。Dubbo自動生成的自適應例項,只有@Adaptive修飾的方法才有具體的實現。所以,Protocol$Adpative類中,也只有export和refer這兩個方法有具體的實現,其餘方法都是丟擲異常。