深入dubbo內核(1):dubbo的自動發現機制

首先了解一下java的spi

  • spi的設計目標:

    面向對象的設計思想是模塊之間基於接口編程,模塊之間不可以對實現類進行硬編碼,一旦代碼涉及了具體的實現類就違反了可插拔的原則,如果要替換一種實現就要修改代碼。

  • Java spi提供了這樣一種機制:

    爲某個接口尋找服務實現的機制,類似於IOC的思想,將裝配的控制權轉移到代碼之外。

  • Java spi的具體約定:

    當服務的提供者(provider),提供了一個接口多種實現的時候,一般都會在jar包的/META-INF/services/目錄下創建該接口的同名文件,文件的內容是該接口的具體實現類名稱,而當外部加載這個模塊的時候就可以通過jar包的/META-INF/services/中的配置文件得到具體的實現類,並且實例化,完成模塊的裝配。

  • dubbo的自動發現機制基於jdk標準的spi進行了擴展:

    JDK 標準的 SPI 會一次性實例化擴展點所有實現,如果有擴展實現初始化很耗時,但如果沒用上也加載,會很浪費資源

    增加了對擴展點 IoC 和 AOP 的支持,一個擴展點可以直接 setter 注入其它擴展點

  • dubbo spi的具體約定:

    spi文件存儲路徑在/META-INF/dubbo/internal目錄下,並且文件名爲接口全路徑名。文件內定義格式爲:擴展名+具體類名

  • 擴展點加載使用的註解

/**
*擴展點接口必須有SPI註解
*/

com.alibaba.dubbo.common.extension.SPI
/**
*擴展點自適應裝配
*/

com.alibaba.dubbo.common.extension.Adaptive
/**
*擴展點自動激活
*/

com.alibaba.dubbo.common.extension.Activate
/**
*dubbo使用ExtensionLoader類實現擴展點的自動加載
*/

com.alibaba.dubbo.common.extension.ExtensionLoader
  • getExtensionLoader()返回擴展點ExtensionLoader並載入緩存
public class ExtensionLoader<T> {
// 緩存ExtensionLoader
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
/**
* 獲得擴展點的ExtensionLoader
*/

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 如果key存在則不替換value
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
}
  • 構造方法
 /**
* 擴展點class對象
*/

private final Class<?> type;
/**
* 管理所有的擴展點,用於IOC和AOP
*/

private final ExtensionFactory objectFactory;
private ExtensionLoader(Class<?> type) {
this.type = type;
// 在這裏會加載ExtensionFactory自適應擴展點
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
}

在構造方法中同樣使用擴展點加載爲objectFactory賦值,用於IOC.並且EXTENSION_LOADERS是一個ConcurrentHashMap,保證ExtensionLoader的單例。

  • getAdaptiveExtension()獲取自適應擴展裝飾類對象
 public class Holder<T> {
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
/**
* 獲取自適應擴展裝飾類對象
*/

public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}

加載自適應擴展點的時候同樣使用了緩存,並且使用了單例模式,但是在這裏使用了一個Holde對象,Holder的value是被Volatile關鍵字修飾的,這樣做是爲了避免在使用單例模式的時候返回null。
單例模式爲什麼要使用Volatile關鍵字修飾:http://mp.weixin.qq.com/s/2UYXNzgTCEZdEfuGIbcczA

 /**
* 創建自適應擴展點實例
* @see #injectExtension(java.lang.Object) 依賴注入
*/

private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}

獲得自適應擴展點的實例後,injectExtension()用於對自適應擴展點進行依賴注入。

 private volatile Class<?> cachedAdaptiveClass = null;
/**
* 加載自適應擴展點Class對象
*/

private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

這裏會首先加載擴展點所有實現類的Class對象,然後判斷cachedAdaptiveClass 是否爲null,做不同的操作。

  • getExtensionClasses() 加載擴展點所有實現類的Class對象
// 緩存所有擴展點實現類的class對象
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
/**
* 加載擴展點實現類Class對象到緩存
* Map<String, Class<?>> key 擴展點實現類名稱 value擴展點實現類Class對象
* 同樣使用單例模式,map維護在Holder中
*/

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;
}
/**
* 擴展點加載路徑
*/

private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
/**
* 加載擴展點實現類Class對象
*
*/

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()
+ ": " + Arrays.toString(names));
}
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;
}

loadFile是正真加載所有點實例到緩存的方法。

  • loadFile() 緩存所有擴產展點實現類的class對象
// 自適應擴展點實例
private volatile Class<?> cachedAdaptiveClass = null;
// 自動激活
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
// 擴展點實現類緩存
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
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"));
try {
String line = null;
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;
int i = line.indexOf('=');
if (i > 0) {
// 擴展點名稱
name = line.substring(0, i).trim();
// 實現類全路徑類名稱
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 獲得擴展點實現類class對象
Class<?> clazz = Class.forName(line, true, classLoader);
// 擴展點實現類必須是該擴展點的子類
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
/**
* @see com.alibaba.dubbo.common.extension.Adaptive
* <li>如果擴展點實現類上存在 Adaptive 註解 ,則緩存起來。<li/>
* <li>一個擴展點實現類只允許存在一個,Adaptive 註解在類上的實現類</li>
*/

if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else {
/**
* 如果擴展點實現類是對擴展點的一個裝飾類
* 加載到的擴展點有拷貝構造函數,則判定爲擴展點 Wrapper類。
* 允許有多個裝飾類
*/

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();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
String[] names = NAME_SEPARATOR.split(name);
/**
* 目前dubbo中所有的擴展點名稱和實現類都是一一對應的不存在一對多的關係
*/

if (names != null && names.length > 0) {
/**
* 緩存實現類有 Activate 註解 的擴展點實現類的 value
*/

Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
/**
* 緩存擴展點實現類,class對象做爲key
*/

if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
/**
* 緩存擴展點實現類,name對象做爲key
*/

Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}

至此擴展點的實現類都會被緩存起來。但是對於有Adaptive,Active註解的擴展點實現類會有不同的處理。並且一個擴展點接口只允許有一個Adaptive註解在類上的實現類。
觀察getAdaptiveExtensionClass()方法會發現如果Adaptive註解上方法上,會動態代理生成一個XXX$Adaptive的代理類,裏面是一個非常複雜的過程。

  • 總結:dubbo的spi中大量的運用了緩存,單例的設計模式,裝飾模式和充分的考慮了併發問題,值得我們借鑑。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章