在很多場合,系統需要根據上下文、參數決定邏輯。例如支付,使用微信、支付寶、銀聯支付邏輯不一樣,比較直接的寫法是用 【if】判斷,走不同的分支;另外,設計模式裏面有一種工廠模式也適用這種場景。【Dubbo】裏也有很多這種場景(多協議等等),但是【Dubbo】使用SPI(Service Provider Interface) + Adaptive機制來應付這種場景。這種機制更靈活、更具擴展性。
1 Dubbo SPI
區別於JDK原生的SPI,【Dubbo】自己實現了一套SPI機制實時加載具體的實現類。org.apache.dubbo.common.extension.ExtensionLoader類是Dubbo SPI機制的核心,看一下【ExtensionLoader】的幾個核心方法。
public class ExtensionLoader<T> {
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
private final Class<?> type;
private final ExtensionFactory objectFactory;
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
private volatile Class<?> cachedAdaptiveClass = null;
private String cachedDefaultName;
private Set<Class<?>> cachedWrapperClasses;
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {}
/**
* Get activate extensions.
*
* @param url url
* @param values extension point names
* @param group group
* @return extension list which are activated
* @see org.apache.dubbo.common.extension.Activate
*/
public List<T> getActivateExtension(URL url, String[] values, String group) {}
/**
* 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) {}
@SuppressWarnings("unchecked")
public T getAdaptiveExtension(){}
}
【ExtensionLoader】使用了單例模式,既有靜態成員、又有實例成員。
1.1 【ExtensionLoader】靜態成員
- EXTENSION_LOADERS 靜態變量 ConcurrentHashMap 用來存儲各SPI接口對應【ExtensionLoader】實例,每個SPI接口的ExtensionLoader都是一個單例,key是SPI接口類;
- EXTENSION_INSTANCES 靜態變量 ConcurrentHashMap 用來存儲所有已加載的實現類的實例, key是實現類;
- <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) 靜態方法 獲取SPI接口(type)的ExtensionLoader實例;
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } //筆者注 不是接口類型 不允許創建ExtensionLoader if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } //筆者注 沒有@SPI註解 也不允許創建ExtensionLoader if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } //筆者注 如果有創建過則直接使用,沒有則創建一個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; }
1.2 【ExtensionLoader】實例成員
-
type 實例變量 當前【ExtensionLoader】實例關聯的SPI接口;
- objectFactory 實例變量 ExtensionFactory 用來給SPI接口實例自動注入屬性,類似於Spring框架的IOC;
- cachedNames 實例變量 ConcurrentMap 緩存實現類對應的擴展名
- cachedClasses 實例變量 用來緩存擴展名對應的實現類
- cachedActivates 實例變量 ConcurrentHashMap 如果實現類有@Activate註解,則將註解緩存下來
- cachedInstances 實例變量 ConcurrentHashMap 用來緩存擴展名對應的實現類實例
- cachedAdaptiveInstance 實例變量 用來緩存SPI接口的Adaptive類的實例
- cachedAdaptiveClass 實例變量 用來緩存SPI接口的Adaptive類
- cachedDefaultName 實例變量 用來緩存SPI接口的默認實現類的擴展名,註解@SPI的value
- cachedWrapperClasses 實例變量 緩存SPI接口的包裝類
- T getExtension(String name) 實例方法 獲取擴展名[name]對應的實現類實例
從磁盤中(META-INF/dubbo/external、META-INF/dubbo/internal、META-INF/dubbo、META-INF/services)加載對應SPI接口的實現類,找到指定擴展名[name]的類,創建實例並自動注入相關屬性。
自動注入只對有且僅有一個參數且參數類型不是基本類型的setter方法,AdaptiveExtensionFactory.getExtension,通過實現類SpringExtensionFactory跟Spring容器結合起來注入Spring的Bean。
public class ExtensionLoader<T> {
//筆者注:如果已經加載過則使用,否則加載實現類並返回name對應的實例
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//筆者注 當前是loader中沒有緩存,則去創建
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
//筆者注 創建name對應擴展實例
private T createExtension(String name) {
//筆者注 獲取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);
//筆者注 如果有包裝類 則包裝對應的實例
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);
}
}
// 筆者注: 對實例自動注入屬性,只對只有一個參數且參數類型不是基本類型(Integer、Double等)的setter方法注入。
//注入的對象是通過ExtensionFactory的Adaptive類AdaptiveExtensionFactory獲取,
//AdaptiveExtensionFactory跟Spring的容器結合起來了
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
/**
* 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;
}
//筆者注 取SPI接口的實現類,首先從緩存取,取不到則從磁盤文件里加載實現類
//(META-INF/dubbo/external、META-INF/dubbo/internal、META-INF/dubbo、META-INF/services)
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;
}
}
- T getAdaptiveExtension() 實例方法 獲取SPI接口對應的Adaptive類的實例。
Dubbo有兩種方法創建Adaptive類,其一是在某個實現類上添加註解@Adaptive則表明這個方法就是該SPI接口的Adaptive類,其二是Dubbo自動生成一個Adaptive類文件並編譯成class對象。
自動生成的Adaptive類只對SPI接口中有@Adaptive註解的方法做處理,沒有@Adaptive註解的方法生成的方法體直接是"throw new UnsupportedOperationException"。有註解的方法生成的方法體實現的邏輯是:
- 找URL對象:類型URL的參數,沒有則在其它參數中尋找"返回類型爲URL"的方法並獲得URL對象,沒有則報錯。
- 在URL中依次找keys集合中的key對應的value作爲實際擴展名,都沒找到則使用SPI接口默認擴展名;keys集合來源於方法上@Adaptive註解的value數組;如果數組爲空,則用SPI接口的類名(駝峯結構轉爲xx.yy.zz形式);實際的代碼形如String extName = url.getParameter("key1", url.getParameter("key2", url.getParameter("key3", "a")))。
- 利用【ExtensionLoader】獲取擴展名對應的實現類實例。
- 調用實際實例的方法,返回結果。
public class ExtensionLoader<T> {
//筆者注 獲取SPI接口對應的Adaptive類的實例,先從緩存中取,沒有則自動生成一個Adaptive類創建實例
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//筆者注 沒有則自動生成一個Adaptive類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
private Class<?> createAdaptiveExtensionClass() {
//筆者注 自動生成一個Adaptive類
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);
}
}
public class AdaptiveClassCodeGenerator {
private static final String CODE_CLASS_DECLARATION = "public class %s$Adaptive implements %s {\n";
private static final String CODE_METHOD_DECLARATION = "public %s %s(%s) %s {\n%s}\n";
private static final String CODE_METHOD_ARGUMENT = "%s arg%d";
private static final String CODE_METHOD_THROWS = "throws %s";
private static final String CODE_UNSUPPORTED = "throw new UnsupportedOperationException(\"The method %s of interface %s is not adaptive method!\");\n";
private static final String CODE_URL_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n";
private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\n";
//筆者注 生成Adaptive類,拼接類的字符串,然後編譯加載
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
code.append(generatePackageInfo());
code.append(generateImports());
code.append(generateClassDeclaration());
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
}
- List<T> getActivateExtension(URL url, String[] values, String group) 實例方法 獲取激活的實現類實例,values是擴展名列表("-name"表示剔除該擴展名對應的實現類)、group一般就是provider(提供方)、consumer(消費方)
邏輯如下
- 如果不剔除默認(即values[]沒有"-default")則先收集[默認激活實例];
- 依次獲取values中實現類的實例("-name"表示剔除name對應的實例,即不獲取);
- 根據"default"在values位置確定[默認激活實例]的位置,如果沒有"default"則表示[默認激活實例]在最前面,values[]裏面對應的實例依次放在後面;
public class ExtensionLoader<T> {
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> activateExtensions = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : asList(values);
//筆者注 如果不剔除默認(即沒有"-default")則先收集[默認激活實例]
//[默認激活實例]有幾個特點
//(1)實現類上有@Activate註解且跟group和url匹配
//(2)實現類擴展名不在values中("-name"或name)
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
//筆者注 依次獲取values中實現類的實例(-name表示剔除,不獲取)。根據"default"位置決定默認[激活實例]位置
List<T> loadedExtensions = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
//筆者注 如果是"default"說明之前的實現類實例,要放在[默認激活實例]之前
if (DEFAULT_KEY.equals(name)) {
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(0, loadedExtensions);
loadedExtensions.clear();
}
} else {
loadedExtensions.add(getExtension(name));
}
}
}
//其餘的 放在[默認激活實例]之後
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(loadedExtensions);
}
return activateExtensions;
}
}
2 SPI在dubbo中應用
【Dubbo】廣泛使用SPI機制,所有@SPI註解的接口都是一個SPI擴展點,如下圖:
3 示例
1 首先定義一個@SPI註解的接口,默認實現類是"a"
package com.focuse.jdkdemo.dubbospi;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI("a")
public interface DubboSpiTest {
@Adaptive
String read(URL url);
void write(String a);
}
2 定義2個實現類DubboSpiTestImplA、DubboSpiTestImplB
package com.focuse.jdkdemo.dubbospi;
import org.apache.dubbo.common.URL;
public class DubboSpiTestImplA implements DubboSpiTest{
public String read(URL url) {
return "Hello A!!";
}
public void write(String a){
}
}
package com.focuse.jdkdemo.dubbospi;
import org.apache.dubbo.common.URL;
public class DubboSpiTestImplB implements DubboSpiTest{
public String read(URL url) {
return "Hello B!!";
}
public void write(String a){
}
}
3 創建spi文件 META-INF/dubbo/com.focuse.jdkdemo.dubbospi.DubboSpiTest
a=com.focuse.jdkdemo.dubbospi.DubboSpiTestImplA
b=com.focuse.jdkdemo.dubbospi.DubboSpiTestImplB
4 ExtensinoLoader獲取DubboSpiTest的Adaptive類的實例
package com.focuse.jdkdemo.dubbospi;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import java.util.HashMap;
/**
* @author :
* @date :Created in 2020/6/6 下午3:08
* @description:
* @modified By:
*/
public class DubboSpiApplication {
public static void main(String[] args) throws Exception{
//獲取DubboSpiTest接口的adaptive類
DubboSpiTest dubboSpiTest = ExtensionLoader.getExtensionLoader(DubboSpiTest.class).getAdaptiveExtension();
HashMap<String, String> params = new HashMap<>();
//上下文中指定實現類,因爲DubboSpiTest.read方法上註解@Adaptive沒有value值,
//所以上下文的key就是DubboSpiTest類名(駝峯轉xxx.yyy.zz形式)
params.put("dubbo.spi.test", "b");
URL context = new URL("dubbo", "DubboSpiTest", 0, params);
String result = dubboSpiTest.read(context);
System.out.println("*************************");
System.out.println(result);
System.out.println("*************************");
}
}
在上下文中指定類實現類用"b"(因爲DubboSpiTest.read方法上註解@Adaptive沒有value值,所以上下文的key就是DubboSpiTest類名(駝峯轉xxx.yyy.zz形式)),所以實際調用的是DubboSpiTestImplB.read()
如果不指定,則會使用默認的"a"
DubboSpiTest$Adaptive是自動生成的DubboSpiTest的Adaptive類,擴展名先從上下文中取,沒有則用默認的"a",拿到擴展名後再獲取擴展名對應的實例。
package com.focuse.jdkdemo.dubbospi;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class DubboSpiTest$Adaptive implements com.focuse.jdkdemo.dubbospi.DubboSpiTest {
public void write(java.lang.String arg0) {
throw new UnsupportedOperationException("The method public abstract void com.focuse.jdkdemo.dubbospi.DubboSpiTest.write(java.lang.String) of interface com.focuse.jdkdemo.dubbospi.DubboSpiTest is not adaptive method!");
}
public java.lang.String read(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("dubbo.spi.test", "a");
if (extName == null)
throw new IllegalStateException("Failed to get extension (com.focuse.jdkdemo.dubbospi.DubboSpiTest) name from url (" + url.toString() + ") use keys([dubbo.spi.test])");
com.focuse.jdkdemo.dubbospi.DubboSpiTest extension = (com.focuse.jdkdemo.dubbospi.DubboSpiTest) ExtensionLoader.getExtensionLoader(com.focuse.jdkdemo.dubbospi.DubboSpiTest.class).getExtension(extName);
return extension.read(arg0);
}
}