1.Dubbo中的SPI機制
阿新 • • 發佈:2020-12-28
1.JAVA SPI
java spi的具體約定為:當服務的提供者,提供了服務介面的一種實現之後,在jar包的META-INF/services/目錄裡同時建立一個以服務介面命名的檔案。該檔案裡就是實現該服務介面的具體實現類。而當外部程式裝配這個模組的時候,就能通過該jar包META-INF/services/裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入。 基於這樣一個約定就能很好的找到服務介面的實現類,而不需要再程式碼裡制定。jdk提供服務實現查詢的一個工具類java.util.ServiceLoader
1.1介面
1.2具體實現public interface UserService { public String getName(int id); }
public class MyUserServiceImpl implements UserService{
@Override
public String getName(int id) {
return "get -> name";
}
}
1.3SPI目錄檔案
位置
檔案內容
com.example.spi.service.MyUserServiceImpl
1.4演示spi實現
1.5 SPI原理 通過上面簡單的demo,可以看到最關鍵的實現就是ServiceLoader這個類,可以看下這個類的原始碼,如下:public static void main(String[] args) { Iterator<UserService> iterator = ServiceLoader.load(UserService.class).iterator(); while (iterator.hasNext()){ UserService service = iterator.next(); System.out.println(service.getName(11)); } }
2.Dubbo SPI dubbo作為一個高度可擴充套件的rpc框架,也依賴於java的spi,並且dubbo對java原生的spi機制作出了一定的擴充套件,使得其功能更加強大。 首先,從上面的java spi的原理中可以瞭解到,java的spi機制有著如下的弊端:public final class ServiceLoader<S> implements Iterable<S> { //掃描目錄字首 private static final String PREFIX = "META-INF/services/"; // 被載入的類或介面 private final Class<S> service; // 用於定位、載入和例項化實現方實現的類的類載入器 private final ClassLoader loader; // 上下文物件 private final AccessControlContext acc; // 按照例項化的順序快取已經例項化的類 private LinkedHashMap<String, S> providers = new LinkedHashMap<>(); // 懶查詢迭代器 private java.util.ServiceLoader.LazyIterator lookupIterator; // 私有內部類,提供對所有的service的類的載入與例項化 private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; String nextName = null; //... private boolean hasNextService() { if (configs == null) { try { //獲取目錄下所有的類 String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { //... } //.... } } private S nextService() { String cn = nextName; nextName = null; Class<?> c = null; try { //反射載入類 c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { } try { //例項化 S p = service.cast(c.newInstance()); //放進快取 providers.put(cn, p); return p; } catch (Throwable x) { //.. } //.. } } }
- 只能遍歷所有的實現,並全部例項化。
- 配置檔案中只是簡單的列出了所有的擴充套件實現,而沒有給他們命名。導致在程式中很難去準確的引用它們。
- 擴充套件如果依賴其他的擴充套件,做不到自動注入和裝配。
- 擴充套件很難和其他的框架整合,比如擴充套件裡面依賴了一個Spring bean,原生的Java SPI不支援。
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER})
public class ProviderHelloFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
System.out.println("hello ok====================================>>>>>");
return invoker.invoke(invocation);
}
}
然後,需要在duboo SPI的掃描目錄下,新增配置檔案,注意配置檔案的名稱要和擴充套件點的介面名稱對應起來:
檔案內容
firstFilter=com.example.dubbo.ProviderHelloFilter
還需要在dubbo的spring配置中顯式的宣告,使用上面自己實現的filter:
<dubbo:provider owner="xxx" timeout="6000" version="1.0.0" retries="0" executes="1000" filter="firstFilter"/>
最後,啟動dubbo,呼叫service驗證。
至此,dubbo的spi的demo也完成了。
dubbo spi的原理和jdk的實現稍有不同,具體實現類是
com.alibaba.dubbo.common.extension.ExtensionLoader
1.先呼叫loadExtensionClasses方法從下面三個目錄來載入介面的實現類名
META-INF/dubbo/internal/
META-INF/dubbo/
META-INF/services/
2.呼叫createExtension方法反射例項化實現類物件並快取起來
3.最後在呼叫filter的時候使用name找到對應的實現呼叫
3.總結
關於spi的詳解到此就結束了,總結下spi能帶來的好處:
- 不需要改動原始碼就可以實現擴充套件,解耦。
- 實現擴充套件對原來的程式碼幾乎沒有侵入性。
- 只需要新增配置就可以實現擴充套件,符合開閉原則。