(一)什麼是SPI機制?Java中的SPI機制是如何實現的?
(1)首先先說一下JavaSPI機制(Service Provider Interface)其實說白了就是定義一個接口,但是可以有多個實現該接口的實現類,其實也是一種服務發現機制。
- 其實SPI機制的本質就是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類。這樣可以在運行時,動態爲接口替換實現類
(2)那麼Dubbo中的SPI機制和Java中的SPi機制又有什麼區別呢?
-
首先Java中的SPI機制是基於接口的編程+策略模式+配置文件的組合方式實現的動態加載機制
-
其實具體的JavaSPI的實現就是:
- 1.你先定義一個A接口,比如是com.wangwei.A這個接口
- 2.然後你需要在src/main/resources/ 下建立 /META-INF/services 目錄下定義和A接口相同的文件com.wangwei.A名字,然後需要在這個com.wangwei.A文件中 定義一些你需要實現這個接口的實現類這裏有多個實現類的
- 3.然後你在使用的時候需要去遍歷這個接口的所有實現類,然後拿到你想要拿到的實現類,然後使用,是這樣的一個流程。
那麼其實JavaSPI機制的底層實現原理:
其實主要ServiceLoader這個類進行去加載的
public final class ServiceLoader<S>
implements Iterable<S>
{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
這裏中間就直接省略了。。。。。。
private class LazyIterator implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
JavaSP機制的缺點:
- 雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載並實例化一遍。如果你並不想用某些實現類,它也被加載並實例化了,這就造成了浪費。獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類。
- 多個併發多線程使用ServiceLoader類的實例是不安全的。
- 擴展如果依賴其他的擴展,做不到自動注入和裝配
- 不提供類似於Spring的IOC和AOP功能
- 擴展很難和其他的框架集成,比如擴展裏面依賴了一個Spring bean,原生的Java SPI不支持
(二)dubbo的SPI機制底層是如何實現的?
(1)dubbo中的SPI機制
- 其實爲什麼在dubbo中又重新定義了一套dubbo的SPI機制,就是因爲java中的SPI機制的一些缺點,不能滿足dubbo的使用場景或者說效率沒有那麼高效
- 所以dubbo自己封裝了一套更強大的SPI機制,直接封裝到了ExtensionLoader 類中,通過 ExtensionLoader,我們可以加載指定的實現類。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑,並且可以直接給實現類進行一個命名,然後在使用的時候,直接通過@SPI(“名字”)去找到具體的實現類,比如:random(名字)=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance(實現類)
(2)dubbo中SPI機制的使用流程:
- 首先如果你想給一個接口需要用dubbo的SPI機制實現,那麼首先你需要給這個接口上加上@SPI(“使用默認的實現類的名字”)這個註解
- 然後需要在你接口中的方法上加上一個@Adaptive註解,這個註解的核心功能就是:會爲該方法生成對應的代碼。方法內部會根據方法的參數,來決定使用哪個擴展,@Adaptive註解用在類上代表實現一個裝飾類,類似於設計模式中的裝飾模式
- 然後通過你在配置文件中配置好並實現你需要的實現類,然後供其進行調用完成,類似下面
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance
(3)dubbo是如何實現的這個增強版的SPI機制的?源碼級別
- 首先dubbo其實是通過 ExtensionLoader 的 getExtensionLoader 方法獲取一個ExtensionLoader 實例,然後再通過 ExtensionLoader 的 getExtension 方法獲取拓展類對象
- getExtension方法中的核心就是創建一個擴展對象
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
// 獲取默認的拓展實現類
return getDefaultExtension();
}
// Holder,顧名思義,用於持有目標對象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(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;
}
- 然後到createExtension()這個方法中主要的流程是:
- 通過 getExtensionClasses 獲取所有的拓展類
- 通過反射創建拓展對象
- 向拓展對象中注入依賴(這裏使用的是 Dubbo IOC 與 AOP 的具體實現)
- 將拓展對象包裹在相應的 Wrapper 對象中(這裏使用的是 Dubbo IOC 與 AOP 的具體實現)
private T createExtension(String 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 (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 循環創建 Wrapper 實例
for (Class<?> wrapperClass : wrapperClasses) {
// 將當前 instance 作爲參數傳給 Wrapper 的構造方法,並通過反射創建 Wrapper 實例。
// 然後向 Wrapper 實例中注入依賴,最後將 Wrapper 實例再次賦值給 instance 變量
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("...");
}
}
- 然後是getExtensionClasses()方法去加載,如果 classes 仍爲 null,則通過 loadExtensionClasses 加載拓展類
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;
}
- 然後是loadExtensionClasses()方法,主要做了兩件事:一是對 SPI 註解進行解析,二是調用 loadDirectory 方法加載指定文件夾配置文件
private Map<String, Class<?>> loadExtensionClasses() {
// 獲取 SPI 註解,這裏的 type 變量是在調用 getExtensionLoader 方法時傳入的
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// 對 SPI 註解內容進行切分
String[] names = NAME_SEPARATOR.split(value);
// 檢測 SPI 註解內容是否合法,不合法則拋出異常
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension...");
}
// 設置默認名稱,參考 getDefaultExtension 方法
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;
}
總結dubbo的SPI機制的特點:(符合Dubbo的微內核+插件的總體設計思想)
首先dubbo的核心都是基於Dubbo的微內核+插件的機制,而SPI機制更是dubbo中最核心之一的地方,他的設計符合開閉原則,有很好的擴展性。
- 對Dubbo進行擴展,不需要改動Dubbo的源碼,自定義的Dubbo的擴展點實現,是一個普通的Java類,Dubbo沒有引入任何Dubbo特有的元素,對代碼侵入性幾乎爲零。
- 將擴展註冊到Dubbo中,只需要在ClassPath中添加配置文件。使用簡單。而且不會對現有代碼造成影響。符合開閉原則
- Dubbo的擴展機制支持IoC,AoP等高級功能
- Dubbo的擴展機制能很好的支持第三方IoC容器,默認支持Spring Bean,可自己擴展來支持其他容器,比如Google的Guice
- 切換擴展點的實現,只需要在配置文件中修改具體的實現,不需要改代碼。使用方便
(三)dubbo中的SPI機制和Java中的SPI機制有什麼區別?
看完上面的講解,想必你自己肯定也能猜到dubbo的SPI機制和Java中的SPI機制有什麼區別,不然我這篇博客不是白寫了嗎,哭唧唧。。。。歡迎探討