## Java SPI
SPI的全名為Service Provider Interface, java spi就是提供這樣的一個機制:為某個接口尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。
SPI的使用是尋找服務實現,例如,我們在項目A中定義了接口Developer,之后可以使用ServiceLoader加載Developer的實現類,實現類可能有B項目或C項目實現。
A項目:
```
package com.shisj.study.dubbo.spi.Developer;
public interface Developer {
public String getPrograme();
}
```
B項目中提供了兩種實現類:
```
package com.shisj.study.dubbo.spi.impl;
public class JavaDeveloper implements Developer{
public String getPrograme() {
return "Java";
}
}
```
```
package com.shisj.study.dubbo.spi.impl;
public class PythonDeveloper implements Developer{
public String getPrograme() {
return "Python";
}
}
```
在B項目的classpath下創建META-INF/services文件夾,內部包含com.shisj.study.dubbo.spi.Developer 文件,這是接口的全名。里面的內容是該借接口的實現類;
```
com.shisj.study.dubbo.spi.impl.JavaDeveloper
com.shisj.study.dubbo.spi.impl.PythonDeveloper
```
將B項目打包成jar包并引用至A項目中,然后我們就可以通過ServiceLoader查找實現類,
```
public ServiceLoader<Developer> serviceloader = ServiceLoader.load(Developer.class);
for (Developer dev : serviceloader) {
System.out.println("out." + dev.getPrograme());
}
// out.Java
// out.Python
```
## Dubbo擴展
dubbo擴展點加載從JDK標準的SPI(Service Provider Interface)擴展點發現機制加強而來。具體的配置文件在classpath的/META-INF/dubbo/internal文件夾里面。dubbo的擴展機制與spi不同,其文件中的內容記錄了`key=class`,原因是:
>當擴展點的static字段或方法簽名上引用了三方庫, 如果三方庫不存在,會導致類初始化失敗, Extension標識Dubbo就拿不到了,異常信息就和配置對應不起來。
比如: Extension("mina")加載失敗, 當用戶配置使用mina時,就會報找不到擴展點, 而不是報加載擴展點失敗,以及失敗原因。
### ExtensionLoader
在dubbo源碼中,對可擴展點的加載都是通過ExtensionLoader類獲取實例的,例如:
```
// 獲取Compiler接口的實現類
ExtensionLoader<Compiler> loader1 = ExtensionLoader.getExtensionLoader(Compiler.class);
Compiler compile1 = loader1.getAdaptiveExtension();
System.out.println(compile1.getClass());
Compiler compile2 = loader1.getExtension("jdk");
System.out.println(compile2.getClass());
```
這樣做的目的是擴展性強,使用者可以無侵入的方式自己進行實現接口供dubbo使用。
ExtensionLoader類內部保存著所有接口的實現類,并記錄了缺省Adaptive實現類和缺省的擴展類,我們可以通過屬性大致了解ExtensionLoader的功能。下面是實例屬性:
```
private final Class<?> type; // ExtensionLoader 創建時指定的類型
private final ExtensionFactory objectFactory; // ExtensionLoader的擴展工廠
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>(); // 記錄了實現類的class與名稱的對應關系
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();// 保存map,內部保存了實現類name與實現類class的對應關系
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>(); //保存有@Activate注解的實現類
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); //保存實現類name與實例
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); //缺省的實例
private volatile Class<?> cachedAdaptiveClass = null; // 缺省的實現類
private String cachedDefaultName; // 默認的實現類名稱,@SPI中指定
private volatile Throwable createAdaptiveInstanceError; // 創建是的錯誤
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
```
除此之外,ExtensionLoader類內部的靜態屬性保存所有擴展類與ExtensionLoader、實例的映射。
```
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
```
**加載過程**
首先根據class從EXTENSION_LOADERS查找對應的ExtensionLoader,如果為null則創建一個ExtensionLoader,指定其type為要查找的class。此時雖然獲取了ExtensionLoader,但還未查找接口的實現類,調用getExtension()或getAdaptiveExtension()等方法時才會真正的查找;getExtensionClasses()方法會依次查找下面3個路徑的文件,讀取內容獲得`name=class`鍵值對,并保存到相應的屬性中。
```
META-INF/services/
META-INF/dubbo/
META-INF/dubbo/internal/
```
下面是解析文件并保存的過程:
* cachedDefaultName 如果ExtensionLoader的type有SPI注解且設置了value,那么這個value就是該接口默認的實現類名稱cachedDefaultName,例如`Compiler`接口使用了注解`@SPI("javassist")`,那么Compiler的ExtensionLoader的cachedDefaultName為javassist。cachedDefaultName的目的是getDefaultExtension()獲取默認的實現類,或查找不到@Adaptiv的實現類時,使用cachedDefaultName創建Adaptive的實現類。
* 按照順序從3個文件夾中,讀取以type的全名命名的文件,解析name和class,如果實現類有@Adaptive注解,設置cachedAdaptiveClass為該實現類;
* 如果沒有@Adaptive,首先判斷實現類是否有將要查找的接口作為參數的構造方法,如果有添加到cachedWrapperClasses變量中,
* 如果沒有這種構造方法,判斷是否有@Activate注解,有的話保存到cachedActivates中;cachedNames保存所有的class和name的映射關系
* 最終,將name與實現類class的映射保存在cachedClasses中。
至此,ExtensionLoader針對給的的接口,查找了所有的擴展,并取得了默認實現類的名稱,Adaptive實現類class(可能沒有,后續獲取是dubbo會創建),以及name與實現類class的映射關系。
有了這些信息,在createExtension(name)時就會根據name獲得class然后newInstance獲取實例。
## dubbo擴展測試
我們自己實現一個MyCompiler,其實現了Compiler接口
```
public class MyCompiler implements Compiler {
public Class<?> compile(String code, ClassLoader classLoader) {
return new JavassistCompiler().compile(code, classLoader);
}
}
```
在/META-INF/services/com.alibaba.dubbo.common.compiler.Compiler文件中加入下面的映射
```
mycp=com.shisj.study.dubbo.loader.MyCompiler
```
測試中,可以通過mycp獲取到實現類MyCompiler
```
Compiler compile2 = loader1.getExtension("mycp");
System.out.println(compile2.getClass());//class com.shisj.study.dubbo.loader.MyCompiler
```
**getAdaptiveExtension內部邏輯**
getAdaptiveExtension首先會查找接口具有@Adaptive注解的實現類作為Adaptive類,如果沒有dubbo會動態創建一個新的類,內部會從url取參數,如果沒有則取默認的值,來自于接口的@SPI注解,如`@SPI("javassist")`的值。
**getExtension()**
獲得Adaptive類的實例后,會將其傳入cachedWrapperClasses,包裝為Wrapper類