輔助鏈接
* [Dubbo系列之 (一)SPI擴展 ]
* [Dubbo系列之 (二)Registry注冊中心-注冊(1)]
* [Dubbo系列之 (二)Registry注冊中心-注冊(2)]
一、基礎鋪墊
1、@SPI 、@Activate、 @Adaptive
-
a、對於 @SPI,Dubbo默認的特性擴展接口,都必須打上這個@SPI,標識這是個Dubbo擴展點。如果自己需要新增dubbo的擴展點我們就需要新增接口,並且這個接口必須標注@SPI.
-
b、@SPI可以填入一個值,這個值代表某個擴展點的名稱,一般作為默認擴展點。
-
c、@Activate,這個注解可以打在方法上或者類上,主要的作用是自動的激活某個擴展點。
-
d、@Adaptive 注解為自適應注解,該注解可以標注在方法上或者類上。這個注解特別有用,當它標注在類上時,說明該類是AdaptiveExtension擴展,如果標注在方法上,說明該方法可以根據參數自適應的返回值。需要注意的是該方法的參數必須有一個是URL類型或者方法參數對象內部必須有一個URL類型參數。它是通過這個URL所帶的參數進行自適應返回。當@Adaptive標注在方法上時,dubbo的spi擴展機制會動態生成一個XXXX$Adaptive擴展類,然后實現被@Adaptive標注的方法。內部的實現是通過ExtensionLoader.getActivateExtension 和傳入的URL 來返回具體哪個擴展點實現。
2、ExtensionLoader
-
a、該方法就是我們對dubbo 擴展點的入口,他跟java的spi的ServiceLoader功能是一樣的,都是加載某個擴展點的所有擴展具體實現。
-
b、ExtensionLoader 查找具體擴展點實現,是去查找類路徑下 META-INF/dubbo, META-INF/dubbo/internal,META-INF/services目錄下的擴展點接口名相同的文件。並加載文件內的具體擴展點。
-
c、入口是靜態方法ExtensionLoader.getExtensionLoader(),返回某個擴展點的ExtensionLoader<?>的具體事例
-
d、ExtensionLoader<?>.getAdaptiveExtension 是得到自適應的擴展點類,如果某個類打上@Adaptive,則返回該類,否則系統通過javassit創建一個。
-
e、ExtensionLoader<?>.getActivateExtension(URL url,key) 可以根據URL.get(key)的值(該值就是擴展點名)得到一個激活的擴展點。注解 @Activate 標注的為默認的激活擴展點,可以通過-default來不激活默認擴展點。
-
f、ExtensionLoader<?>.getExtension 通過name 得到想要的擴展點,並且如果有Wrapper類型擴展點,會對其進行包裹返回。
-
g、當一個具體的擴展點的構造函數的參數是其擴展點接口類型,即構造函數是TypeImpl(IType type) && TypeImpl implements IType 時,可以稱這種擴展點為Wraper擴展點。那么所返回的非Wraper擴展點都會被包裹成Wraper類型,如果有多個Wraper的擴展點,那么會一層一層的包裹,但是誰包裹誰,不固定。
-
h、當一個具體的擴展點的實現有其setter方法且沒有被@DisableInject標注和注入類型不是原始類型(long,int,short,String,BigDecimal等)時,該setter方法會被調用,注入其需要的Object,而Object的實例原來就是通過ExtensionFactory工廠。
3、ExtensionFactory
-
a、首先ExtensionFactory 這個也是SPI的擴展點,它也是通過ExtensionLoader來加載的,他加載的時機是某個具體其他擴展點通過ExtensionLoader.getExtensionLoader()加載時,內部會調用ExtensionLoader私有構造函數,在這個構造函數內部來加載這個工廠,並且加載的這個工廠是自適應工程實例。
-
b、ExtensionLoader獲取具體擴展點的來源就是通過ExtensionFactory擴展點的自適應擴展點AdaptiveExtensionFactory。
-
c、AdaptiveExtensionFactory 的實現可以知道擴展點的實例注入來源,包括Spring 容器(通過SpringExtensionFactory)和 SpiExtensionFactory。
二、特性測試
接着我們對上面的特性,進行一輪測試,驗證其結果。
新建一個擴展接口
package org.apache.dubbo.mytest;
@SPI("first")
public interface InvokerProtocol {
@Adaptive("getInvoker")
Invoker getInvoker(String name, URL url);
}
默認的擴展點為first,並且getInvoker方法被@Adaptive標注。接着我們在META-INF/services 下新建一個文件名為org.apache.dubbo.mytest.InvokerProtocol文件,里面填寫這個擴展點的實現,如:
first=org.apache.dubbo.mytest.protocol.FirstInvokerProtocol
測試1
@Test
public void testDefaultExtension(){
ExtensionLoader<InvokerProtocol> extensionLoader = ExtensionLoader.getExtensionLoader(InvokerProtocol.class);
InvokerProtocol firstInvokerProtocol = extensionLoader.getExtension("first");
Assert.check(firstInvokerProtocol instanceof FirstInvokerProtocol); //true
InvokerProtocol defaultExtension = extensionLoader.getDefaultExtension();
Assert.check(defaultExtension ==firstInvokerProtocol); //true
}
從上面我們驗證了SPI("first")標注的名稱,指定為默認的擴展實現類。接着我們在org.apache.dubbo.mytest.InvokerProtocol文件,接着新增一個擴展點,
second=org.apache.dubbo.mytest.protocol.SecondInvokerProtocol,並把SecondInvokerProtocol類打上@Activate("second")注解。注意,FirstInvokerProtocol沒有打上@Activate注解。接着再一次測試
測試2
@Test
public void testActivateExtension(){
ExtensionLoader<InvokerProtocol> extensionLoader = ExtensionLoader.getExtensionLoader(InvokerProtocol.class);
URLBuilder urlBuilder = new URLBuilder();
urlBuilder.setProtocol("test");
urlBuilder.setPath("defaultInvokerProtocol");
urlBuilder.addParameter("invokerProtocol", "first");
List<InvokerProtocol> invokerProtocol = extensionLoader.getActivateExtension(urlBuilder.build(), "invokerProtocol");
Assert.check(invokerProtocol.size() == 2); //true
}
從該上面例子,驗證了getActivateExtension 可以通過URL 來激活指定的擴展實現,並且還會返回被@Activate的擴展點類,說明@Activate被標注的擴展點類被默認激活。當我們在org.apache.dubbo.mytest.InvokerProtocol文件,接着新增二個擴展點,如下
firstwraper=org.apache.dubbo.mytest.protocol.FirstWraperInvokerProtocol
secondwraper=org.apache.dubbo.mytest.protocol.SecondWraperInvokerProtocol
這個2個擴展點是包裹擴展點,它的構造函數如下:
public FirstWraperInvokerProtocol(InvokerProtocol invokerProtocol) {
this.invokerProtocol = invokerProtocol;
}
這時,我們運行test1的單測,發現斷言失敗,並且知道返回的擴展點實例被這2個wraper擴展點包裹。
測試3
@Test
public void testDefaultExtension(){
ExtensionLoader<InvokerProtocol> extensionLoader = ExtensionLoader.getExtensionLoader(InvokerProtocol.class);
InvokerProtocol firstInvokerProtocol = extensionLoader.getExtension("first");
InvokerProtocol defaultExtension = extensionLoader.getDefaultExtension();
}
從上面,得知firstInvokerProtocol 類型是FirstWraperInvokerProtocol 並且持有SecondWraperInvokerProtocol類型,而SecondWraperInvokerProtocol最終持有我們的FirstInvokerProtocol.
接着測試,我們獲取下AdaptiveExtension擴展,目前我們只在擴展點的接口方法上打上@Adaptive,即getInvoker方法。
測試4
@Test
public void testExtensionAdaptive(){
InvokerProtocol adaptiveExtension = ExtensionLoader.getExtensionLoader(InvokerProtocol.class).getAdaptiveExtension();
System.out.println(adaptiveExtension);
}
可以發現框架動態為我們生產了一個類名為InvokerProtocol$Adaptive的自適應擴展點類。並且實現其getInvoker方法,方法內容為(整理了下)。
public class InvokerProtocol$Adaptive implements InvokerProtocol {
public Invoker getInvoker(String arg0, URL arg1) {
if (arg1 == null)
throw new IllegalArgumentException("url == null");
URL url = arg1;
String extName = url.getParameter("getInvoker", "first");
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.mytest.InvokerProtocol) name " +
"from url (" + url.toString() + ") use keys([getInvoker])");
InvokerProtocol extension = ExtensionLoader.getExtensionLoader(InvokerProtocol.class).getExtension(extName);
return extension.getInvoker(arg0, arg1);
}
}
我們知道其內部通過URL參數,根據url.getParameter得到我們我們需要的具體需要的擴展點名,接着ExtensionLoader.getExtension得到具體擴展點。
接着我們自己實現一個名為AdaptiveInvokerProtocol的自適應的擴展點類,並把AdaptiveInvokerProtocol標注上@Adaptive,接着在org.apache.dubbo.mytest.InvokerProtocol文件下,填入:
adaptive=org.apache.dubbo.mytest.protocol.AdaptiveInvokerProtocol
再一次執行
測試5
@Test
public void testExtensionAdaptive(){
InvokerProtocol adaptiveExtension = ExtensionLoader.getExtensionLoader(InvokerProtocol.class).getAdaptiveExtension();
System.out.println(adaptiveExtension);
}
可以知道,自適應擴展點變為我們實現的AdaptiveInvokerProtocol,框架沒有在為我們創建一個自適應的擴展點類。
三、源碼跟蹤
我們通過如果下幾個問題來跟蹤我們的代碼。
1、為什么擴展點需要標注@SPI
我們在獲取擴展點的入口為ExtensionLoader.getExtensionLoader。在這個靜態方法里做了@SPI注解判斷。
// 說明該接口上是否有SPI注解
private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}
@SuppressWarnings("unchecked")
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 an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// 並為每種類型的擴展創建一個具體類型的ExtensionLoader<?>的實例,並放入EXTENSION_LOADERS緩存中。
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;
}
2、@Activate注解的作用如何實現的
我們通過 getActivateExtension 得到激活的擴展點,看下面的具體注釋:
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,說明不排除框架,默認激活的擴展點
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses(); // 加載具體擴展點類,在加載過程中會把打上@Activate注解的類放到緩存cachedActivates中,其中key為擴展點名,Value為Activate注解
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
//得到Activate注解上的group和value.
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;
}
//判斷是否匹配,如果匹配加到activateExtensions。
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
// 上面的if獲取的是滿足條件默認激活的擴展點類。這里是URL參數直接指定需要的擴展點放到loadedExtensions
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)) {
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;
}
3、@Adaptive注解的作用如何實現的
自適應的擴展點實現類只能有一個,我們通過getAdaptiveExtension來獲取,內容如下:
public T getAdaptiveExtension() {
//首先cachedAdaptiveInstance用來存放自適應擴展點實例的Holder
Object instance = cachedAdaptiveInstance.get();
if (instance == null) { // 如果不存在
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) { //鎖住該Holder
instance = cachedAdaptiveInstance.get();//再次獲取,應為該方法可能是並發環境下,所以鎖定雙層判斷
if (instance == null) {
try {
instance = createAdaptiveExtension(); //這里創建一個自適應擴展點實例
cachedAdaptiveInstance.set(instance);// 並放入Holder中
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
接着我們來看下,這個createAdaptiveExtension是如何創建的。
private T createAdaptiveExtension() {
try {
// 步驟非常清晰,首先通過getAdaptiveExtensionClass得到自適應的擴展點的Class,然后調用newInstance得到一個實例
//接着調用 injectExtension方法為這個實例注入相關需要的熟悉,通過這個實例的setter方法。
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(); // 這里還是通過getExtensionClasses加載擴展類,之后分析
if (cachedAdaptiveClass != null) { //當調用完 getExtensionClasses方法后,如果存在自適應的擴展點類,會被賦值給cachedAdaptiveClass,那么直接返回,
return cachedAdaptiveClass;
}
// 如果沒有找到被@Adaptive標注的類,為其創建一個自適應的擴展點類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
// 創建的一個自適應的擴展點的代碼比較簡單,就是StringBuilder 創建一個java類文件內容,然后通不過dubbo提供的compiler工具進行編譯為字節碼,通過javaassit完成。
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
System.out.println(code);
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);
}
4、ExtensionLoader是如何處理Wraper擴展點的
在通過getExtensionClasses 加載擴展點類時,會判斷這個類時否是Wraper,判斷如下:
private boolean isWrapperClass(Class<?> clazz) {
try {
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
即構造函數的參數否為自身擴展點,並把這些類放入一個名為cachedWrapperClasses的緩存中。
private void cacheWrapperClass(Class<?> clazz) {
if (cachedWrapperClasses == null) {
cachedWrapperClasses = new ConcurrentHashSet<>();
}
cachedWrapperClasses.add(clazz);
}
當我們調用ExtensionLoader<?>.getExtension 時,
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// 如果名稱為true,返回默認的擴展點
if ("true".equals(name)) {
return getDefaultExtension();
}
//從holder中得到名為name的擴展點
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) { //這里也是鎖定雙層判斷
instance = holder.get();
if (instance == null) {
instance = createExtension(name); // 創建該擴展點實例,並放入該Holder中
holder.set(instance);
}
}
}
return (T) instance;
}
private T createExtension(String name) {
// 調用getExtensionClasses 得到擴展點類
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 為空,clazz.newInstance()一個實力
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 為擴展點setter注入
injectExtension(instance);
// 這里,對包裹擴展點進行for循環,然后調用wrapperClass.getConstructor(type).newInstance ,一次循環包裹對象
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);
}
}
5、ExtensionLoader是如何注入擴展點的。
在 injectExtension方法中可以得到答案,這個方法被createAdaptiveExtension和createExtension調用,都是在創建完具體擴展點實例后,對其注入。
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;
}
6、ExtensionFactory工廠在ExtensionLoader內部的運用
在injectExtension方法中,我們看到objectFactory.getExtension(pt, property),即從objectFactory得到需要注入的屬性對象。objectFactory 的實例化,我們是在ExtensionLoader私有構造函數中。
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
而私有構造函數在getExtensionLoader的靜態方法上調用。並且objectFactory還是自適應的擴展點實現。所以我們在加載一個擴展點時,首先框架內部會實例化這個ExtensionFactory工廠。並且這個自適應工程名為AdaptiveExtensionFactory,內容就是就是組合Spi和Spring的ExtensionFactory。
7、getExtensionClasses 的流程是什么。
流程比較清晰,不具體的貼代碼了。簡潔流程調用如下 :
getExtensionClasses (這里是在緩存cachedClasses取,緩存取不到下一步) ->loadExtensionClasses(主要從META-INF/services,META-INF/dubbo,META-INF/dubbo/internal文件下找具體的擴展點類)
在這過程中把一些不同類型的擴展點放入到緩存中。具體看loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) 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標注,放入緩存cachedAdaptiveClass
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
// 如果該類是Wrapper類型,放入緩存cachedWrapperClasses
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
//這里判斷是否有默認構造函數,
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
通過loadClass我們知道,ExtensionLoader內部緩存主要包括如下三個:
-
cachedClasses:存放沒有被@Adaptive標注的且不是Wrapper擴展點類
-
cachedWrapperClasses :存放Wrapper類型的擴展點類
-
cachedAdaptiveClass :存放@Adaptive標注的類,沒有被@Adaptive標注時,系統自動創建一個。
