牛逼的框架,看似複雜難懂,思路其實很清晰。---me
上篇文章,在整體擴充套件思路上進行了原始碼分析,比較粗糙,現在就某些點再詳細梳理下。
dubbo SPi的擴充套件,基於一類、三註解。
- 一類是ExtensionLoader類
- 三註解是@SPI、@Adaptive、@Activate
本文總結dubbo是如何使用ExtensionLoader實現擴充套件的,詳細看看它是怎麼設計的,為何這樣設計?
1. ExtensionLoader屬性
首先是ExtensionLoader包含的屬性,如下。
主要包含常量定義(如dubbo SPi路徑META-INF/services/等)、載入的型別type、一系列快取容器。
2. dubbo是如何載入SPI擴充套件類的呢?是一次性把所有的擴充套件都讀到記憶體中嗎?
當然不是,dubbo不是一次性把所有的SPI擴充套件檔案都載入。而是根據型別,即type,進行載入。
可以看到上圖中有兩個關鍵欄位,如下
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(); private final Class<?> type;
其中 EXTENSION_LOADERS 是一個全域性 擴充套件載入器 的容器,key為擴充套件介面,即type型別的SPI介面;value為介面對應的ExtensionLoader例項。
在上篇文章中說到,dubbo載入SPI與JDK載入SPI類似,讀取指定路徑檔案中的定義。載入路徑如下:
Map<String, Class<?>> loadExtensionClasses()
void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst)
void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL)
void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name)
在第二步loadDirectory時,傳入路徑和type(介面的全限定名),在方法內部拼出SPI路徑,如下:
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) { String fileName = dir + type; ....... }
所以,得到的結論是:
- 每個型別對應一個ExtensionLoader載入器;
- 載入器載入擴充套件實現類時,只讀取type對應的實現類。
3. 為何要設計自適應類?帶來了什麼好處?
dubbo官網這樣解釋:“在 Dubbo 中,很多擴充都是通過 SPI 機制進行載入的,比如 Protocol、Cluster、LoadBalance 等。有時,有些擴充並不想在框架啟動階段被載入,而是希望在擴充方法被呼叫時,根據執行時引數進行載入。這聽起來有些矛盾。擴充未被載入,那麼擴充方法就無法被呼叫(靜態方法除外)。擴充方法未被呼叫,擴充就無法被載入。對於這個矛盾的問題,Dubbo 通過自適應擴充機制很好的解決了。自適應擴充機制的實現邏輯比較複雜,首先 Dubbo 會為擴充介面生成具有代理功能的程式碼。然後通過 javassist 或 jdk 編譯這段程式碼,得到 Class 類。最後再通過反射建立代理類,整個過程比較複雜。”
dubbo擴充套件非常多,所有的底層關鍵介面都可以擴充套件,為了不在啟動的時候載入所有類,而想在方法呼叫時載入,即懶漢方式。
所以引入了自適應擴充套件機制,它的好處:
- 封裝所有擴充套件類,根據URL引數動態選擇具體實現類
- 框架啟動時,減少不必要擴充套件的載入損耗
自適應類分為兩種,一種是動態生成的,一種是自定義。前者使用@Adaptive在方法上,後者使用該註解在類上。
區別就在於此,修飾在類上,表示該類為自適應類,無序dubbo再動態生成。
(1)自定義自適應類
這種方式的自適應類比較少,目前有ExtensionFactory、Compiler介面在使用。
AdaptiveExtensionFactory是ExtensionFactory的自適應類,它持有所有ExtensionFactory的實現,然後根據type和name遍歷所有容器載入擴充套件類物件,上篇文章有介紹。
@Adaptive public class AdaptiveExtensionFactory implements ExtensionFactory { private final List<ExtensionFactory> factories; public AdaptiveExtensionFactory() {
//持有ExtensionFactory所有介面實現類 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); } @Override 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; } }
同理,AdaptiveCompiler是Compiler的自適應類,會根據name使用指定的編譯器,預設情況使用JavassistCompiler。
@Adaptive public class AdaptiveCompiler implements Compiler { private static volatile String DEFAULT_COMPILER; public static void setDefaultCompiler(String compiler) { DEFAULT_COMPILER = compiler; } @Override public Class<?> compile(String code, ClassLoader classLoader) { Compiler compiler; ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { compiler = loader.getExtension(name); } else { compiler = loader.getDefaultExtension(); } return compiler.compile(code, classLoader); } }
(2)動態生成的自適應類
如上篇文章中,介面如下:
@SPI("human") public interface HelloService { String sayHello(); @Adaptive String sayHello(URL url); }
動態生成的自適應類,如下:
package com.lagou.service; import org.apache.dubbo.common.extension.ExtensionLoader; public class HelloService$Adaptive implements com.lagou.service.HelloService {
public java.lang.String sayHello() { throw new UnsupportedOperationException("The method public abstract java.lang.String com.lagou.service.HelloService.sayHello() of interface com.lagou.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.lagou.service.HelloService) name from url (" + url.toString() + ") use keys([hello.service])"); com.lagou.service.HelloService extension = (com.lagou.service.HelloService)ExtensionLoader.getExtensionLoader(com.lagou.service.HelloService.class).getExtension(extName); return extension.sayHello(arg0); } }
其中沒有被@Adaptive修飾的方法,生成的方法只有一個異常語句。被修飾的方法會根據URL引數及ExtensionLoader擴充套件機制,動態獲取使用的擴充套件實現類。
(3)dubbo是怎麼區分是否要動態生成,還是直接使用定義好的自適應類呢?
這個涉及到ExtensionLoader中的屬性cachedAdaptiveClass,其快取了自定義的自適應類,在SPI擴充套件載入的時候進行識別並快取。
如果沒有自定義的自適應類,則不會用到該快取。
涉及到自適應類的操作包含兩個步驟:載入+使用
1)首先,載入。程式碼在loadClass方法中,如下:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) { if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz); } }
當類被Aaptive修飾時,則將載入的class快取到cachedAdaptiveClass中,從原始碼中可以看到,只允許一個SPI介面具有一個自定義的自適應類。
private void cacheAdaptiveClass(Class<?> clazz) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getName() + ", " + clazz.getName()); } }
2)其次是使用。獲取自適應類的路徑為:
public T getAdaptiveExtension() private T createAdaptiveExtension() private Class<?> getAdaptiveExtensionClass()
看看getAdaptiveExtensionClass的程式碼,你就明白原來這麼簡單。
private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
該邏輯有三步:
- 載入對應type的SPI擴充套件類,包括自定義的自適應類,即上邊描述的載入快取過程
- 判斷是否有自定義的自適應類,有則直接返回
- 沒有,則動態生成
我們來看看是怎麼動態生成的?原始碼寫的也很清楚。
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); }
總共四步:
- 生成自適應類的原始碼code,通過字串拼接,如package、import、class、method等,具體不在這展開
- 獲取類載入器
- 獲取編譯器,此處會呼叫到Compiler的自定義的自適應類
- 對code進行編譯,得到自適應類的class
4. 自動啟用類是怎麼回事?一般應用在什麼地方?
自動啟用,官網描述:“對於集合類擴充套件點,比如:Filter
, InvokerListener
, ExportListener
, TelnetHandler
, StatusChecker
等,可以同時載入多個實現,此時,可以用自動啟用來簡化配置”。
比如,過濾器Filter,可以使用@Activate,自動啟用自定義的過濾器,以使與業務相關的控制能參與到dubbo的呼叫鏈中。如日誌記錄、方法執行時間等。
對ExtensionLoader來說,如何識別和使用自動啟用類呢?
在ExtensionLoader中的cachedActivates屬性,快取了type 擴充套件類中被@Activate註解修飾的類資訊。
Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
key為定義在SPI檔案中的key,Object為Activate註解的例項(注意這裡不是存的被修飾類對應的例項,而是Activate註解本身)。
涉及到該快取的操作有兩處,一處是載入SPI檔案時存入,一處是通過URL等資訊判斷是否需要啟用對應的類。
(1)Activate類是如何識別並載入的?
在loadClass中,有一段涉及Activate的程式碼,如下:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) { ...... String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) {
//快取具有Activate註解的擴充套件類 cacheActivateClass(clazz, names[0]); }
...... }
其中name為SPi檔案中定義的key,從下邊原始碼可以看出,cachedActivates map中存的是Activate例項。
private void cacheActivateClass(Class<?> clazz, String name) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(name, activate); } else { // support com.alibaba.dubbo.common.extension.Activate com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class); if (oldActivate != null) { cachedActivates.put(name, oldActivate); } } }
(2)Activate類又是如何使用的呢?
getActivateExtension方法是獲取Activate類的具體實現(下方程式碼有省略),我們可以看到分為兩步:
- 載入擴充套件類,並獲取cacheActivates
- 根據URL引數與Activate宣告的規則進行匹配
public List<T> getActivateExtension(URL url, String[] values, String group) { List<T> exts = new ArrayList<>(); List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values); if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) { //載入擴充套件類,獲取到cachedActivates getExtensionClasses(); //遍歷cachedActivates,獲取與URL、group等引數匹配的Activate類 for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) { String name = entry.getKey(); Object activate = entry.getValue(); String[] activateGroup, activateValue; if (activate instanceof Activate) { //Activate類宣告的group activateGroup = ((Activate) activate).group(); //Activate類宣告的value activateValue = ((Activate) activate).value(); } else { continue; } //1.組匹配;2.value匹配 if (isMatchGroup(group, activateGroup) && !names.contains(name) && !names.contains(REMOVE_VALUE_PREFIX + name) && isActive(activateValue, url)) { exts.add(getExtension(name)); } } exts.sort(ActivateComparator.COMPARATOR); } ...... return exts; }
以Filter為例,在ProtocolFilterWrapper.refer構造dubbo介面的invoker後,會對invoker增加過濾器,原始碼如下:
@Override public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { if (UrlUtils.isRegistry(url)) { return protocol.refer(type, url); } return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER); }
傳給buildInvokerChain方法三個引數:
- 第一個是為dubbo介面生成的invoker
- 第二個是常量:reference.filter,對應URL中的某個key
- 第三個是常量:consumer,對應group
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) { Invoker<T> last = invoker; List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group); if (!filters.isEmpty()) { for (int i = filters.size() - 1; i >= 0; i--) { final Filter filter = filters.get(i); final Invoker<T> next = last; last = new Invoker<T>() { ...... }; } } return last; }
buildInvokerChain方法,構建過濾器鏈的邏輯比較簡單:
- dubbo介面的invoker在過濾器連結串列的最後,即在執行的時候,最後執行實際介面呼叫
- 載入Filter對應的與group等引數匹配的自動啟用類
- 構建過濾器鏈
(3)舉個例子
比如我們自定義一個過濾器
@Activate(group = {CommonConstants.CONSUMER,CommonConstants.PROVIDER}) public class DubboInvokeTimeFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { long startTime = System.currentTimeMillis(); try { // 執行方法 return invoker.invoke(invocation); } finally { System.out.println("invoke time:"+(System.currentTimeMillis()-startTime) + "毫秒"); } } }
在META- INF.dubbo中增加org.apache.dubbo.rpc.Filter檔案,內容如下:
timeFilter=com.lagou.filter.DubboInvokeTimeFilter
5. dubbo的包裝器類是什麼,有何用處?
dubbo中包裝器是SPI擴充套件類的一種,準確的說是一個代理類,實現了對擴充套件類的AOP。
- 實現SPI介面
- 持有SPI介面的物件
如上文中的ProtocolFilterWrapper,就是protocol介面對應的一個包裝器
public class ProtocolFilterWrapper implements Protocol { private final Protocol protocol; public ProtocolFilterWrapper(Protocol protocol) { if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; } ...... @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { if (UrlUtils.isRegistry(invoker.getUrl())) { return protocol.export(invoker); } return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER)); } @Override public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { if (UrlUtils.isRegistry(url)) { return protocol.refer(type, url); } return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER); } ...... }
並且,該類也是定義在dubbo-rpc模組下的SPI org.apache.dubbo.rpc.Protocol檔案中,如下:
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper mock=org.apache.dubbo.rpc.support.MockProtocol
在通過某Protocol實現類建立物件時,會自動為該物件封裝該包裝器。這樣也就實現了對invoker的過濾攔截。
(1)dubbo是如何實現包裝器類的識別和載入的呢?
要從ExtensionLoader的cachedWrapperClasses屬性說起,該屬性快取了包裝器類。
private Set<Class<?>> cachedWrapperClasses;
載入擴充套件類時,loadClass方法中會對class進行判斷,如下:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) { if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } }
- 判斷是否為包裝器類
- 快取包裝器類
看isWrapperClass方法,呼叫的是Class獲取建構函式的方法,如果不存在具有type型別引數的建構函式,則拋異常,通過攔截返回false。
private boolean isWrapperClass(Class<?> clazz) { try { clazz.getConstructor(type); return true; } catch (NoSuchMethodException e) { return false; } }
快取包裝器類,程式碼很簡單
private void cacheWrapperClass(Class<?> clazz) { if (cachedWrapperClasses == null) { cachedWrapperClasses = new ConcurrentHashSet<>(); } cachedWrapperClasses.add(clazz); }
(2)dubbo是如何對某例項進行包裝的呢?
通過例子來說明,extensionLoader.getExtension(extension) 這句程式碼是根據副檔名獲取對應的擴充套件類例項的,包裝器就在這個過程中把原來的例項進行封裝代理了。
public static void main(String[] args) { // 獲取擴充套件載入器 ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class); // 遍歷所有的支援的擴充套件點 META-INF.dubbo Set<String> extensions = extensionLoader.getSupportedExtensions(); for (String extension : extensions){ String result = extensionLoader.getExtension(extension).sayHello(); System.out.println(result); } }
定義HelloWrapper類:
public class HelloWrapper implements HelloService { private HelloService helloService; public HelloWrapper(HelloService helloService) { this.helloService = helloService; } @Override public String sayHello() { System.out.println("sayHello------>"); return helloService.sayHello(); } @Override public String sayHello(URL url) { System.out.println("sayHello------>url"); return helloService.sayHello(url); } }
測試輸出:
sayHello------>url
wang url
dubbo如何對例項進行封裝的呢?
核心邏輯在createExtension方法中,即根據傳入的name建立擴充套件類例項的方法中。
private T createExtension(String name) { 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); } injectExtension(instance);
//wrapper 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); } }
從程式碼可以看出分為兩步:
- getExtensionClasses方法載入擴充套件類,存到cachedWrapperClasses中
- 遍歷cachedWrapperClasses,通過構造器例項化包裝器,同時還為包裝類進行依賴注入。(無論多少wrapper,都會一層一層進行封裝)
這樣返回給使用者的擴充套件例項,即為經過層層封裝的擴充套件類。
6. 總結
- ExtensionLoader是實現dubbo可擴充套件的核心類,為各擴充套件點提供框架層面的支援,
- ExtensionLoader的邏輯看著複雜,其實思路比較簡單,第一是擴充套件點載入,第二是建立指定的擴充套件點例項
- 從程式碼分析,可以看出該類包含了一系列快取容器,這些快取在擴充套件點載入的時候進行識別和儲存
- 在擴充套件點例項建立時,會通過自適應類動態找到目標擴充套件;將自動啟用類應用到擴充套件例項或dubbo的核心invoker上;並將例項封裝到wrapper類中