上篇我們簡(jiǎn)單講了dubbo SPI的使用,沒(méi)有太多代碼,一個(gè)接口和兩個(gè)實(shí)現(xiàn)類(lèi),還有一個(gè)配置文件和測(cè)試類(lèi)。接口和實(shí)現(xiàn)類(lèi)沒(méi)有什么講的,因此入口就從測(cè)試類(lèi)開(kāi)始。
測(cè)試類(lèi)代碼如下
public static void main(String[] args) {
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
/**
* dubbo SPI支持默認(rèn)設(shè)計(jì)
* 配置 {@link com.alibaba.dubbo.common.extension.SPI}的value屬性即可
*/
Robot bumblebee = extensionLoader.getDefaultExtension();
bumblebee.sayHello();
}
我們首先通過(guò) ExtensionLoader 的 getExtensionLoader 方法獲取一個(gè) ExtensionLoader 實(shí)例,然后再通過(guò) ExtensionLoader的 getExtension 方法獲取拓展類(lèi)對(duì)象。
- 我們先看一下
ExtensionLoader的getExtensionLoader方法,getExtensionLoader如下:
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// 非空判斷
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
// SPI 擴(kuò)展點(diǎn)只能是接口
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not interface!");
}
// 判斷是否有 SPI 擴(kuò)展點(diǎn)注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
// 從緩存中獲取對(duì)應(yīng)的ExtensionLoader
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
// 如果緩存中不存在,創(chuàng)建并保存到緩存
if (loader == null) {
// 先在緩存中創(chuàng)建
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
// 獲取
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
這里的代碼比較簡(jiǎn)單,if的判斷是check合法性;然后會(huì)先根據(jù)type在緩存EXTENSION_LOADERS中獲取ExtensionLoader,如果沒(méi)有獲取到會(huì)創(chuàng)建,這段代碼寫(xiě)的比較優(yōu)雅,簡(jiǎn)單的實(shí)現(xiàn)了線(xiàn)程安全
需要說(shuō)明的是EXTENSION_LOADERS是一個(gè)靜態(tài)變量,定義如下
/**
* ExtensionLoader的緩存,key為Class, value為ExtensionLoader的實(shí)例
*/
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
到這里ExtensionLoader.getExtensionLoader(Robot.class)方法已經(jīng)講解完,主要是獲取ExtensionLoader對(duì)象,關(guān)于ExtensionLoader的構(gòu)造方法的講解,放到最后講解。
- 接下來(lái)我們看一下測(cè)試類(lèi)代碼中的
extensionLoader.getExtension("optimusPrime");方法,extensionLoader.getExtension代碼如下:
/**
* Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
* will be thrown.
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
// 參數(shù)校驗(yàn)
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// name為true時(shí),返回默認(rèn)擴(kuò)展點(diǎn), getDefaultExtension為獲取默認(rèn)擴(kuò)展點(diǎn)
if ("true".equals(name)) {
return getDefaultExtension();
}
// 根據(jù)擴(kuò)展點(diǎn)name從緩存 cachedInstances 中獲取擴(kuò)展點(diǎn)的instance
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
// 獲取holder中的instance
Object instance = holder.get();
// 這里用到了double-check locking
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 創(chuàng)建拓展實(shí)例
instance = createExtension(name);
// 設(shè)置實(shí)例到 holder 中
holder.set(instance);
}
}
}
// 返回對(duì)應(yīng)的實(shí)例
return (T) instance;
}
這段代碼不太難,不過(guò)也是一個(gè)線(xiàn)程安全的,使用了double-check locking和Holder,疑問(wèn)為啥要這樣做,使用了double-check不就行了,為啥要用Holder?。
現(xiàn)在說(shuō)一下代碼,先是參數(shù)的校驗(yàn),然后判斷如果name是true,調(diào)用getDefaultExtension方法,返回默認(rèn)的擴(kuò)展點(diǎn);否則調(diào)用createExtension方法創(chuàng)建擴(kuò)展點(diǎn)示例。
現(xiàn)在我們先看一下createExtension方法:
@SuppressWarnings("unchecked")
private T createExtension(String name) {
// 從配置文件中加載所有的拓展類(lèi),可得到“配置項(xiàng)名稱(chēng)”到“配置類(lèi)”的映射關(guān)系表
Class<?> clazz = getExtensionClasses().get(name);
// 非空判斷
if (clazz == null) {
throw findException(name);
}
// 開(kāi)始創(chuàng)建instance
try {
// 根據(jù)class獲取緩存 EXTENSION_INSTANCES 中對(duì)應(yīng)的實(shí)例
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 通過(guò)反射創(chuàng)建實(shí)例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 向?qū)嵗凶⑷胍蕾?lài), dubbo的IOC
injectExtension(instance);
// wrapperClasses處理
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
// 循環(huán)創(chuàng)建 Wrapper 實(shí)例
for (Class<?> wrapperClass : wrapperClasses) {
// 將當(dāng)前 instance 作為參數(shù)傳給 Wrapper 的構(gòu)造方法,并通過(guò)反射創(chuàng)建 Wrapper 實(shí)例。
// 然后向 Wrapper 實(shí)例中注入依賴(lài),最后將 Wrapper 實(shí)例再次賦值給 instance 變量
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
// 返回對(duì)應(yīng)的instance
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
createExtension方法涉及的代碼較多,包含了如下的步驟:
- 通過(guò) getExtensionClasses 獲取所有的拓展類(lèi)
- 通過(guò)反射創(chuàng)建拓展對(duì)象
- 向拓展對(duì)象中注入依賴(lài)
- 將拓展對(duì)象包裹在相應(yīng)的 Wrapper 對(duì)象中
現(xiàn)在我們先看步驟一,分析getExtensionClasses方法。我們?cè)谕ㄟ^(guò)名稱(chēng)獲取拓展類(lèi)之前,首先需要根據(jù)配置文件解析出拓展項(xiàng)名稱(chēng)到拓展類(lèi)的映射關(guān)系表(Map<名稱(chēng), 拓展類(lèi)>),之后再根據(jù)拓展項(xiàng)名稱(chēng)從映射關(guān)系表中取出相應(yīng)的拓展類(lèi)即可。getExtensionClasses代碼如下:
private Map<String, Class<?>> getExtensionClasses() {
// 從緩存 cachedClasses 中獲取
Map<String, Class<?>> classes = cachedClasses.get();
// 雙重檢查
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 加載擴(kuò)展點(diǎn)classes
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
這里也是先檢查緩存,若緩存未命中,則通過(guò) synchronized 加鎖。加鎖后再次檢查緩存,并判空。此時(shí)如果 classes 仍為 null,則通過(guò) loadExtensionClasses 加載拓展類(lèi)。下面分析 loadExtensionClasses 方法的邏輯。
private Map<String, Class<?>> loadExtensionClasses() {
// 獲取 SPI 注解,這里的 type 變量是在調(diào)用 getExtensionLoader 方法時(shí)傳入的
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
// 獲取 SPI 注解的value值
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// 對(duì) SPI 注解內(nèi)容進(jìn)行切分
String[] names = NAME_SEPARATOR.split(value);
// 檢測(cè) SPI 注解內(nèi)容是否合法,不合法則拋出異常
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
// 這是默認(rèn) cachedDefaultName 名稱(chēng)
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// 加載指定文件夾下的配置文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
loadExtensionClasses 方法總共做了兩件事情,一是對(duì) SPI 注解進(jìn)行解析,二是調(diào)用 loadDirectory 方法加載指定文件夾配置文件。SPI 注解解析過(guò)程比較簡(jiǎn)單,無(wú)需多說(shuō)。下面我們來(lái)看一下 loadDirectory 做了哪些事情。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
// fileName = 文件夾路徑 + type 全限定名
String fileName = dir + type.getName();
try {
Enumeration<URL> urls;
// 獲取classLoader
ClassLoader classLoader = findClassLoader();
// 根據(jù)文件名加載所有的同名文件
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//url中的elements
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 加載資源
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
LOGGER.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
loadDirectory 方法先通過(guò) classLoader 獲取所有資源鏈接,然后再通過(guò) loadResource 方法加載資源。我們繼續(xù)跟下去,看一下 loadResource 方法的實(shí)現(xiàn)。
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
String line;
// 按行讀取配置內(nèi)容
while ((line = reader.readLine()) != null) {
// 定位 # 字符
final int ci = line.indexOf('#');
if (ci >= 0) {
// 截取 # 之前的字符串,# 之后的內(nèi)容為注釋?zhuān)枰雎? line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
// 以等于號(hào) = 為界,截取鍵與值
//擴(kuò)展點(diǎn)name
name = line.substring(0, i).trim();
//擴(kuò)展點(diǎn)class路徑
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加載類(lèi),并通過(guò) loadClass 方法對(duì)類(lèi)進(jìn)行緩存
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);
}
}
}
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
loadResource 方法用于讀取和解析配置文件,并通過(guò)反射加載類(lèi),最后調(diào)用 loadClass 方法進(jìn)行其他操作。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.");
}
// 檢測(cè)目標(biāo)類(lèi)上是否有 Adaptive 注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
// 設(shè)置 cachedAdaptiveClass緩存
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
// 檢測(cè) clazz 是否是 Wrapper 類(lèi)型
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<>();
wrappers = cachedWrapperClasses;
}
// 存儲(chǔ) clazz 到 cachedWrapperClasses 緩存中
wrappers.add(clazz);
// 程序進(jìn)入此分支,表明 clazz 是一個(gè)普通的拓展類(lèi)
} else {
// 檢測(cè) clazz 是否有默認(rèn)的構(gòu)造方法,如果沒(méi)有,則拋出異常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果 name 為空,則嘗試從 Extension 注解中獲取 name,或使用小寫(xiě)的類(lèi)名作為 name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
// 切分 name
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
// 如果類(lèi)上有 Activate 注解,則使用 names 數(shù)組的第一個(gè)元素作為鍵,
// 存儲(chǔ) name 到 Activate 注解對(duì)象的映射關(guān)系
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
// 存儲(chǔ) Class 到名稱(chēng)的映射關(guān)系
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
// 存儲(chǔ)名稱(chēng)到 Class 的映射關(guān)系
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
loadClass 方法操作了不同的緩存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。除此之外,該方法沒(méi)有其他什么邏輯了。
關(guān)于Adaptive注解和Activate注解,這里先不作說(shuō)明,后續(xù)會(huì)有說(shuō)明。
回到createExtension方法,通過(guò) getExtensionClasses 獲取所有的拓展類(lèi)這步工作已經(jīng)講解完成了,接著就是通過(guò)反射創(chuàng)建拓展對(duì)象,這個(gè)比較比較簡(jiǎn)單,通過(guò)反射創(chuàng)建,不用講解。
如果沒(méi)有使用到 Dubbo IOC 與 AOP 話(huà),getExtension方法整體就結(jié)束了,該方法就返回了擴(kuò)展點(diǎn)的instance,本文中的測(cè)試類(lèi)的代碼沒(méi)有使用了Dubbo IOC 與 AOP,因此就結(jié)束了。
- 現(xiàn)在開(kāi)始講解
createExtension方法調(diào)用的injectExtension方法,該方法是Dubbo IOC的實(shí)現(xiàn)
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 遍歷目標(biāo)類(lèi)上的所有方法
for (Method method : instance.getClass().getMethods()) {
// 檢查方法是否已set 開(kāi)頭,且方法只有一個(gè)參數(shù),且方法訪(fǎng)問(wèn)級(jí)別是public
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
// 如果方法有 DisableInject 注解修飾,則忽略
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 獲取 setter 方法參數(shù)類(lèi)型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 獲取屬性名,比如 setName 方法對(duì)應(yīng)屬性名 name
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
// 從 ObjectFactory 中獲取依賴(lài)對(duì)象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通過(guò)反射調(diào)用 setter 方法設(shè)置依賴(lài)
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 變量的類(lèi)型為 AdaptiveExtensionFactory,AdaptiveExtensionFactory 內(nèi)部維護(hù)了一個(gè) ExtensionFactory 列表,用于存儲(chǔ)其他類(lèi)型的 ExtensionFactory。Dubbo 目前提供了兩種 ExtensionFactory,分別是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于創(chuàng)建自適應(yīng)的拓展,后者是用于從 Spring 的 IOC 容器中獲取所需的拓展。這兩個(gè)類(lèi)的類(lèi)的代碼不是很復(fù)雜,這里就不一一分析了。
Dubbo IOC 目前僅支持 setter 方式注入,總的來(lái)說(shuō),邏輯比較簡(jiǎn)單易懂。
- 關(guān)于測(cè)試類(lèi)中的
getDefaultExtension方法,獲取默認(rèn)擴(kuò)展點(diǎn),getDefaultExtension代碼如下:
/**
* Return default extension, return <code>null</code> if it's not configured.
*/
public T getDefaultExtension() {
// 獲取擴(kuò)展點(diǎn)的classes
getExtensionClasses();
// 默認(rèn)擴(kuò)展點(diǎn)校驗(yàn)
if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
return null;
}
// 獲取默認(rèn)擴(kuò)展點(diǎn)
return getExtension(cachedDefaultName);
}
這里實(shí)現(xiàn)比較簡(jiǎn)單,不在作說(shuō)明
本篇文章簡(jiǎn)單分別介紹了 Java SPI 與 Dubbo SPI 用法,并對(duì) Dubbo SPI 的加載拓展類(lèi)的過(guò)程進(jìn)行了分析。另外,在 Dubbo SPI 中還有一塊重要的邏輯這里沒(méi)有進(jìn)行分析,即 Dubbo SPI 的擴(kuò)展點(diǎn)自適應(yīng)機(jī)制。該機(jī)制的邏輯較為復(fù)雜,我們將會(huì)在下一篇文章中進(jìn)行詳細(xì)的分析。