趁同事上廁所時間,看完了 Dubbo SPI 的源碼,瞬間覺得 JDK SPI不香了
一、引言
兄弟們,上次的故障結果出來了
還好銷售團隊給力,沒有讓客戶幾千萬的單子丟掉,成功挽回了本次損失
不過內部處罰還是相對嚴重,年終獎懸了
這也告誡我們 要對生產保持敬畏之情!
恰巧最近領導看我在寫 Dubbo 源碼系列,看到我們的項目中用了 SPI 擴展
于是給我一個將功補過的機會,讓我好好的分析分析 Dubbo 的 SPI 的擴展機制,進行組內技術分享
作為一個常年分享 源碼系列 文章的選手,當然不會拒絕!
乾坤未定,你我皆是黑馬,沖!
二、SPI是什么
SPI 全稱 Service Provider Interface ,是 Java 提供的一套用來被第三方實現或者擴展的 API,它可以用來啟用框架擴展和替換組件。
Java SPI 實際上是 基于接口的編程+策略模式+配置文件 組合實現的動態加載機制。
Java SPI 就是提供這樣的一個機制:為某個接口尋找服務實現的機制。
將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。
所以 SPI 的核心思想就是解耦。
三、使用介紹
我們定義一個接口:City
@SPI
public interface City {
String getCityName();
}
實現其兩個類:
- BeijingCity
public class BeijingCity implements City{
@Override
public String getCityName() {
return "北京";
}
}
- TianjinCity
public class TianjinCity implements City{
@Override
public String getCityName() {
return "天津";
}
}
重點來了:我們要在 resources 文件夾下面建立一個路徑:META-INF/dubbo
然后我們建立一個 txt 名為:com.dubbo.provider.SPI.Dubbo.City,如下:
我們在這個文件中寫上各實現類的路徑:
beijing=com.dubbo.provider.SPI.Dubbo.BeijingCity
tianjin=com.dubbo.provider.SPI.Dubbo.TianjinCity
有的朋友可能會問,這里為什么和 Java SPI 的實現不同?
這也正是 Dubbo 實現精準實例化的原因,我們后面也會聊到
測試方法:
public class DubboSPITest {
public static void main(String[] args) {
ExtensionLoader<City> loader = ExtensionLoader.getExtensionLoader(City.class);
City tianjin = loader.getExtension("beijing");
System.out.println(tianjin.getCityName());
}
}
測試結果:
北京
從這里我們可以看出,Dubbo 可以通過 loader.getExtension("beijing") 精確的生成我們需要的實例
精確生成是如何實現的呢?我們繼續往下看
四、原理介紹
在源碼介紹之前,我們先說幾個原理細節,防止大家后面的源碼看迷糊
1、SPI注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* default extension name
*/
String value() default "";
/**
* scope of SPI, default value is application scope.
*/
ExtensionScope scope() default ExtensionScope.APPLICATION;
}
在 SPI 注解中,存在兩個參數:value、scope
value
- 作用:如果某個 SPI 擴展沒有指定實現類名稱,則會使用 @SPI 注解中指定的默認值
scope:指定 SPI 擴展實現類的作用域( Constants.SINGLETON)
- Constants.FRAMEWORK(框架作用域):實現類在 Dubbo 框架中只會創建一個實例,并且在整個應用程序中共享。
- Constants.APPLICATION(應用程序作用域):實現類在應用程序上下文中只會創建一個實例,并且在整個應用程序中共享。
- Constants.MODULE(模塊作用域):實現類在模塊上下文中只會創建一個實例,并且在整個模塊中共享。
- Constants.SELF(自定義作用域):實現類的作用范圍由用戶自行定義,可以是任何范圍。
當然,這里 Dubbo 默認的是 Constants.APPLICATION,我們也只需要關注這個即可。
五、源碼剖析
1、Loader的創建
我們 Dubbo 的 SPI 從ExtensionLoader.getExtensionLoader(City.class) 開始,看一看其實現方案
public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// 1、校驗
checkDestroyed();
// 2、是否有本地Loader緩存
ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoadersMap.get(type);
// 3、是否有本地Scope緩存
ExtensionScope scope = extensionScopeMap.get(type);
// 4、如果當前的Scope為空
// 4.1 獲取當前接口類的SPI注解
// 4.2 獲取當前注解的scope
// 4.3 放入scope緩存
if (scope == null) {
SPI annotation = type.getAnnotation(SPI.class);
scope = annotation.scope();
extensionScopeMap.put(type, scope);
}
// 5、如果加載器為空且當前是SELF,直接創建loader
if (loader == null && scope == ExtensionScope.SELF) {
loader = createExtensionLoader0(type);
}
// 6、如果當前加載器為空,去父類找加載器
if (loader == null) {
if (this.parent != null) {
loader = this.parent.getExtensionLoader(type);
}
}
// 7、如果父類也沒有實例化,那么實例化并放入緩存
if (loader == null) {
loader = createExtensionLoader(type);
}
// 8、返回加載器
return loader;
}
從上面的源碼我們可以看到,獲取 ExtensionLoader 采用了 緩存 + 父類繼承 的模式
這種繼承機制設計得比較巧妙,可以避免重復加載類,提高系統性能。
2、獲取實例
Dubbo 通過 loader.getExtension("tianjin") 獲取對應的實例
public T getExtension(String name) {
T extension = getExtension(name, true);
return extension;
}
public T getExtension(String name, boolean wrap) {
// 1、校驗
checkDestroyed();
// 2、參數為true,表明采用默認的實現類
// 2.1 我們上面SPI中的value參數,若指定tianjin,則采用tianjin的實現類
if ("true".equals(name)) {
return getDefaultExtension();
}
String cacheKey = name;
if (!wrap) {
cacheKey += "_origin";
}
// 3、查看當前緩存中是否含有該實例
// 3.1 如果當前的cacheKey沒有Holder的話,創建一個
final Holder<Object> holder = getOrCreateHolder(cacheKey);
// 4、如果實例為空,采用DCL機制創建實例
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
private Holder<Object> getOrCreateHolder(String name) {
// 1、獲取當前name的Holder
Holder<Object> holder = cachedInstances.get(name);
// 2、沒有則創建并扔進緩存
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
// 3、返回
return holder;
}
Holder 類是一個簡單的容器類,用于保存某個對象的引用
在 Dubbo 的 ExtensionLoader 類中,Holder 類被用于實現對 SPI 擴展實現類的緩存
Holder 結構如下:
public class Holder<T> {
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
我們創建實例一共有以下幾部分:
- 解析文件配置得到對應的類
- 通過實例化創建相關的類
- 初始化之前前置操作
- 依賴注入
- 初始化之后后置操作
- Wrapper 的包裝
- 是否具有生命周期管理的能力
我們挨個的講解
2.1 解析文件配置
Class<?> clazz = getExtensionClasses().get(name);
private Map<String, Class<?>> getExtensionClasses() {
// 1、從緩存中獲取類的信息
Map<String, Class<?>> classes = cachedClasses.get();
// 2、DCL創建(經典的單例設計模式)
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 3、加載類信息并放至緩存中
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
// 1、校驗
checkDestroyed();
// 2、是否有默認的類
// 2.1 我們之前聊過的SPI注解的value機制
cacheDefaultExtensionName();
// 3、這里有三個文件解析器
// 3.1 DubboInternalLoadingStrategy:解析META-INF/dubbo/internal/
// 3.2 DubboLoadingStrategy:解析META-INF/dubbo/
// 3.3 ServicesLoadingStrategy:解析META-INF/services/
// 3.4 解析文件并放至緩存
Map<String, Class<?>> extensionClasses = new HashMap<>();
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy, type.getName());
if (this.type == ExtensionInjector.class) {
loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
}
}
// tianjin:"class com.msb.dubbo.provider.SPI.Dubbo.TianjinCity"
// beijing:"class com.msb.dubbo.provider.SPI.Dubbo.BeijingCity"
return extensionClasses;
}
2.2 實例化創建
// 1、從緩存中獲取
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
// 2、緩存為空則創建并放至緩存
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
}
// 1、獲取當前類的所有的構造方法
// 2、判斷是否有符合的構造方法,若沒有則報錯
// 3、有符合的構造犯法,返回即可
private Object createExtensionInstance(Class<?> type) throws ReflectiveOperationException {
return instantiationStrategy.instantiate(type);
}
2.3 前置處理
類似 Spirng 的前置處理器,之前也說過,感興趣的可以看一下,整體思路區別不大
instance = postProcessBeforeInitialization(instance, name);
private T postProcessBeforeInitialization(T instance, String name) throws Exception {
if (extensionPostProcessors != null) {
for (ExtensionPostProcessor processor : extensionPostProcessors) {
instance = (T) processor.postProcessBeforeInitialization(instance, name);
}
}
return instance;
}
2.4 依賴注入
- 首先,如果依賴注入器為 null,則直接返回傳入的實例。
- 然后,遍歷傳入實例的所有方法,找到所有的 setter 方法。
- 對于每個 setter 方法,如果標注了 @DisableInject 注解,則跳過該方法,不進行注入。
- 如果 setter 方法的參數類型是基本類型,則跳過該方法,不進行注入。
- 如果 setter 方法的參數類型不是基本類型,則嘗試從依賴注入器中獲取該類型對應的實例,并調用該 setter 方法進行注入。
- 如果獲取實例失敗,則記錄錯誤日志。
- 最后,返回注入后的實例。
injectExtension(instance);
private T injectExtension(T instance) {
for (Method method : instance.getClass().getMethods()) {
// 1、如果不是setter方法,直接跳過
if (!isSetter(method)) {
continue;
}
// 2、包含了DisableInject注解,直接跳過
if (method.isAnnotationPresent(DisableInject.class)) {
continue;
}
if (method.getDeclaringClass() == ScopeModelAware.class) {
continue;
}
// 3、如果是基本數據類型,跳過
if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
continue;
}
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 4、依賴注入器中獲取該類型對應的實例,并調用該 setter 方法進行注入
// 4.1 這里直接拿取的ListableBeanFactory->DefaultListableBeanFactory
String property = getSetterProperty(method);
Object object = injector.getInstance(pt, property);
// 5、將當前的對象注入到實例里面
if (object != null) {
method.invoke(instance, object);
}
}
return instance;
}
2.5 后置操作
- 類似 Spirng 的后置處理器,之前也說過,感興趣的可以看一下,整體思路區別不大
instance = postProcessAfterInitialization(instance, name);
private T postProcessAfterInitialization(T instance, String name) throws Exception {
if (instance instanceof ExtensionAccessorAware) {
((ExtensionAccessorAware) instance).setExtensionAccessor(extensionDirector);
}
if (extensionPostProcessors != null) {
for (ExtensionPostProcessor processor : extensionPostProcessors) {
instance = (T) processor.postProcessAfterInitialization(instance, name);
}
}
return instance;
}
2.6 Wrapper 的包裝
在講該部分之前,我們先來看 cachedWrapperClasses 這個緩存的來歷:
在我們上面解析文件配置時,會進行 loadClass,這里不僅會解析正常的類,也會解析 Wrapper 類,方便后面的包裝
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,boolean overridden) {
if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
}
}
從這里我們可以看到,最關鍵的當屬判斷當前的 Class 是不是屬于 WrapperClass
protected boolean isWrapperClass(Class<?> clazz) {
// 1、獲取構造方法
Constructor<?>[] constructors = clazz.getConstructors();
// 2、從構造方法中取出參數為 1 且類型等于當前接口的
for (Constructor<?> constructor : constructors) {
if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == type) {
return true;
}
}
return false;
}
而具體的實現如下:
public class CityWrapper implements City{
private City city;
// 怎樣判斷擴展點還是aop切面呢?
// 通過是否有這樣的一個構造方法來判斷
public CityWrapper(City city) {
this.city = city;
}
@Override
public String getCityName() {
return "文明城市" + city.getCityName();
}
}
了解這個之后,我們再來看看 Dubbo 如何處理這些類似 AOP 的包裝
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
// 1、判斷是否有Wrapper緩存
// 1.1 將緩存放入當前
// 1.2 排序 + 翻轉
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
// 2、當前的wrapper緩存不為空
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
// 循環包裝
for (Class<?> wrapperClass : wrapperClassesList) {
// 3、獲取Wrapper注解,是否需要包裝(正常都是包裝的)
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
// 4、判斷下是否包裝條件
boolean match = (wrapper == null) ||
((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
!ArrayUtils.contains(wrapper.mismatches(), name));
// 5、符合包裝
// 5.1 將當前類封裝至wrapper中
// 5.2 做一些后置處理
if (match) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
通過這種方式,我們可以創建不同的 wrapper,實現 AOP 的作用
讀過 從源碼全面解析 dubbo 服務端服務調用的來龍去脈 和 從源碼全面解析 dubbo 消費端服務調用的來龍去脈 的文章,這時候應該理解最后的那些 過濾器 怎么實現的了
比如,我們現在有兩個 wrapper 類,分別是 CityWrapper 和 CityWrapper2,實現類是 TianjinCity
那么,我們最終 TianjinCity 返回的實例如下:
- CityWrapper
TianjinCity
CityWrapper2
不得不說,這個包裝還是有點秀秀的
2.7 生命周期管理
- 實現 Lifecycle 的接口
initExtension(instance);
六、流程圖
高清圖片私聊博主獲取
七、總結
魯迅先生曾說:獨行難,眾行易,和志同道合的人一起進步。彼此毫無保留的分享經驗,才是對抗互聯網寒冬的最佳選擇。
其實很多時候,并不是我們不夠努力,很可能就是自己努力的方向不對,如果有一個人能稍微指點你一下,你真的可能會少走幾年彎路。