dubbo SPI SPI,全程Service Provider interface, java中的一種藉口擴展機制,將藉口的實現類註明在配置文件中,程式在運行時通過掃描這些配置文件從而獲取全部的實現類。 java 原生的spi將藉口的實現類信息放在META INF/services/文件中。spi ...
dubbo SPI
SPI,全程Service Provider interface, java中的一種藉口擴展機制,將藉口的實現類註明在配置文件中,程式在運行時通過掃描這些配置文件從而獲取全部的實現類。
java 原生的spi將藉口的實現類信息放在META-INF/services/<藉口全限定名>文件中。spi被廣泛應用於各種框架及中間件中,用以提升框架的擴展性。
dubbo也使用spi來提供各種擴展,但是dubbo並未使用java原生的spi,而是實現了自己的spi機制。dubbo的spi實現ExtensionLoader相較於java自帶的ServiceLoader還是有不少改進的,例如為每個實現類賦一個名稱,以k-v存儲在配置文件中,並且在代碼中也可以通過名稱獲取到相應的實現類,另外,ExtensionLoader的載入目錄也比ServiceLoader更多,
ExtensionLoader.getExtensionLoader
這個方法是入口
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//做一些非空檢查
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
//必須是介面,spi是針對介面的擴展機制
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
//必須帶有SPI註解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
//EXTENSION_LOADERS是ExtensionLoader的全局緩存,interface->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;
}
ExtensionLoader.getExtension(String name)
獲取一個ExtensionLoader實例後,通過該方法獲取一個擴展類的實例
private T createExtension(String name) {
//核心邏輯。找到並載入所有的擴展類
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//EXTENSION_INSTANCES是擴展類實例的全局緩存
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//自動註入一些屬性,實現IOC特性
injectExtension(instance);
//對原始實例進行包裝,實現AOP特性
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
//層層包裝
//這裡有個問題,如果包裝類有多個,那麼他們的順序如何???
//spring中對於多個通知類Advice的情況是會進行排序的
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 + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
getExtensionClasses
//載入該介面所有的擴展類,這個方法主要通過雙檢查鎖機制實現了緩存邏輯,
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;
}
loadExtensionClasses
//這個方法返回的類中不包括帶有Adaptive註解的類,以及包裝類
private Map<String, Class<?>> loadExtensionClasses() {
//緩存預設擴展類的名稱
//預設擴展類名稱通過介面上的SPI註解獲取,就是SPI註解的value
cacheDefaultExtensionName();
//將載入的擴展類實例放到該Map中,
Map<String, Class<?>> extensionClasses = new HashMap<>();
//載入 META-INF/dubbo/internal/目錄下的配置文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
//載入 META-INF/dubbo/internal/目錄下alibaba自己的相關介面,通過將org.apache替換為com.alibaba
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//載入 META-INF/dubbo/目錄下的配置文件
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
//同上
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//載入 META-INF/services/目錄下的配置文件
//這裡需要註意的是,由於項目中可能會引入使用jdk ServiceLoader的包,
// 那麼 META-INF/services/目錄下可能存在ServiceLoader的配置文件,而這些文件中存儲的實現類並不是以key-value形式存儲的
// 這樣ExtensionLoader在載入的時候就找不到name,這個後續會進行一些處理
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
//同上
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
可以看出載入了META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/三個目錄下的配置文件,除了載入原介面相關的配置文件,ExtensionLoader額外載入了alibaba自己的相關介面的擴展類。接下來,我們看一下loadDirectory方法。
loadDirectory
//從相關的目錄找到對應介面類型的配置文件,並載入全部擴展類
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
//配置文件全路徑:文件夾+介面名稱,META
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
//首先獲取ClassLoader, 獲取順序是:線程上下文類載入器->載入ExtensionLoader的類載入器->系統類載入器(AppClassLoader)
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
//獲取資源文件的url
urls = classLoader.getResources(fileName);
} else {
//多此一舉??
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//遍歷找到的所有文件,依次載入進來
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//載入一個文件中定義的擴展類
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
loadResource
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
//配置文件必須是utf-8編碼格式
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
//#號後面是註釋,忽略註釋
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
//文件中每一行以:name=extension.class.name格式存儲擴展類,
//以=號作為分隔符,就是properties文件格式
int i = line.indexOf('=');
//這裡少考慮了i==0的情況????
//name可以是空,也就是說可以只有擴展類名,而沒有name,
//實際上這種格式就是ServiceLoader的資源文件,
// 對於name為空的情況的處理邏輯在loadClass方法中
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
//重點關註一下name為空的處理邏輯
//這裡調用Class.forName載入實現類
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
//記錄下載入失敗的類,異常及錯誤信息
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
這裡少考慮了i==0的情況????
loadClass
//載入一個擴展類
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.");
}
//設置adaptive, adaptive只能有一個,如果有多個擴展類上都有Adaptive註解,那麼會拋異常
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
//保存包裝類,
//判斷包裝類就是看這個類有沒有隻有一個參數的構造器,而且參數的類型必須是對應的擴展介面類型
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
//代碼進入這個分支,說明該類是普通的擴展類
//必須要有無參構造器
clazz.getConstructor();
//前面也講過,name是可以為空的,資源文件中可以只有實現類的類名稱
if (StringUtils.isEmpty(name)) {
//通過Extension註解找name的值,
//如果沒有Extension註解,那麼通過類名稱獲取name值,具體處理方法不細說
name = findAnnotationName(clazz);
if (name.length() == 0) {
//Extension註解中的value為空字元串,這種情況拋異常
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
//可以有多個別名,類似spring中bean的別名,多個名稱以逗號分隔
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
//如果類帶有Activate註解,那麼將其緩存下來
cacheActivateClass(clazz, names[0]);
//將
for (String n : names) {
//將名字緩存下來,只緩存第一個名字
cacheName(clazz, n);
//將載入的類放到一路傳進來的extensionClasses中,
//如果有多個別名,每個別名都存儲
saveInExtensionClass(extensionClasses, clazz, name);
}
}
}
}
至此,就查找到了該介面的全部擴展類。
實現IOC特性
前面講到,在createExtension方法中,創建完實例後,會調用injectExtension方法自動組註入一些屬性,
injectExtension
private T injectExtension(T instance) {
try {
//objectFactory是AdaptiveExtensionFactory的一個實例
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
我們首先看一下objectFactory成員是怎麼來的
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
對於一般的介面類型,objectFactory成員就是帶Adaptive註解的ExtensionFactory介面的實現類,其實就是AdaptiveExtensionFactory的一個實例。
AdaptiveExtensionFactory
//該類與dubbo spi的自適應機制相關
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
//載入ExtensionFactory介面的所有實現類
//並緩存到factories中
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
//這裡思考:為什麼AdaptiveExtensionFactory不會迴圈調用構造器???
//原因在ExtensionLoader.loadClass方法中,Adaptive註解的類和包裝類都只緩存下來,不在正常的查找擴展類的範圍內,
//getSupportedExtensions實際上返回的是cachedClasses成員保存的類,是不包括Adaptive註解的類和包裝類的
//所以這裡才不會發生迴圈調用AdaptiveExtensionFactory的構造器
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
//遍歷所有ExtensionFactory實現類,
//返回第一個不是null的值
//我們需要看一下都有哪些ExtensionFactory實現類
//註意,這裡思考一下,AdaptiveExtensionFactory為什麼不會迴圈調用???
//這裡仍然涉及到ExtensionFactory多個實現類排序的問題???
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
這裡仍然涉及到ExtensionFactory多個實現類排序的問題???
這裡通過遍歷所有的ExtensionFactory實現類,找到相應的屬性值。ExtensionFactory實現類有MyExtensionFactory,SpiExtensionFactory,AdaptiveExtensionFactory,SpringExtensionFactory,除去AdaptiveExtensionFactory與自適應機制相關,不起真正的依賴尋找的作用;SpiExtensionFactory是針對帶有SPI註解的類型進行自動註入依賴;
而對與一般的類型,則是使用SpringExtensionFactory來進行依賴註入,在spring的BeanFactory中查找匹配的Bean實例,大概邏輯是:先通過beanName來查找,找不到再通過類型來查找。
實現AOP特性
dubbo的aop的實現略顯簡單,使用靜態代理模式,代理類由用戶實現,可以通過多層包裝實現多級攔截。
如果有多個包裝類的情況下,同樣存在順序的問題, ExtensionLoader有好多地方應該確定類的調用順序,卻沒相應的排序規則,甚至都沒有預留出排序介面,這點spring做得非常好,
spring中凡是涉及到多個平級類的鏈式調用或遍歷查找的,都會實現Ordered介面或PriorityOrdered介面,包括aop中有多個通知類Advice的情況,都會以一定的規則進行排序。