1. 程式人生 > 其它 >1.Dubbo中的SPI機制

1.Dubbo中的SPI機制

技術標籤:dubbojava

1.JAVA SPI java spi的具體約定為:當服務的提供者,提供了服務介面的一種實現之後,在jar包的META-INF/services/目錄裡同時建立一個以服務介面命名的檔案。該檔案裡就是實現該服務介面的具體實現類。而當外部程式裝配這個模組的時候,就能通過該jar包META-INF/services/裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入。 基於這樣一個約定就能很好的找到服務介面的實現類,而不需要再程式碼裡制定。jdk提供服務實現查詢的一個工具類java.util.ServiceLoader 1.1介面
public interface UserService {
public String getName(int id);
}

1.2具體實現
public class MyUserServiceImpl implements UserService{
@Override
public String getName(int id) {
return "get -> name";
}
}

1.3SPI目錄檔案 位置 檔案內容 com.example.spi.service.MyUserServiceImpl 1.4演示spi實現
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));
}
}

1.5 SPI原理 通過上面簡單的demo,可以看到最關鍵的實現就是ServiceLoader這個類,可以看下這個類的原始碼,如下:
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) {
//..
}
//..
}
}
}

2.Dubbo SPI dubbo作為一個高度可擴充套件的rpc框架,也依賴於java的spi,並且dubbo對java原生的spi機制作出了一定的擴充套件,使得其功能更加強大。 首先,從上面的java spi的原理中可以瞭解到,java的spi機制有著如下的弊端:
  • 只能遍歷所有的實現,並全部例項化。
  • 配置檔案中只是簡單的列出了所有的擴充套件實現,而沒有給他們命名。導致在程式中很難去準確的引用它們。
  • 擴充套件如果依賴其他的擴充套件,做不到自動注入和裝配。
  • 擴充套件很難和其他的框架整合,比如擴充套件裡面依賴了一個Spring bean,原生的Java SPI不支援。
dubbo的spi有如下幾個概念: (1) 擴充套件點 :一個介面。 (2) 擴充套件 :擴充套件(介面)的實現。 (3) 擴充套件自適應例項: 其實就是一個Extension的代理,它實現了擴充套件點介面。在呼叫擴充套件點的介面方法時,會根據實際的引數來決定要使用哪個擴充套件。dubbo會根據介面中的引數,自動地決定選擇哪個實現。 (4) @SPI :該註解作用於擴充套件點的介面上,表明該介面是一個擴充套件點。 (5) @Adaptive: @Adaptive註解用在擴充套件介面的方法上。表示該方法是一個自適應方法。Dubbo在為擴充套件點生成自適應例項時,如果方法有@Adaptive註解,會為該方法生成對應的程式碼。 dubbo的spi也會從某些固定的路徑下去載入配置檔案,並且配置的格式與java原生的不一樣,類似於property檔案的格式: firstFilter=com.example.dubbo.ProviderHelloFilter 下面將基於dubbo去實現一個簡單的擴充套件實現。首先,要實現 org.apache.dubbo.rpc.Filter 這個介面,當然這個介面是被註解@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能帶來的好處:
  • 不需要改動原始碼就可以實現擴充套件,解耦。
  • 實現擴充套件對原來的程式碼幾乎沒有侵入性。
  • 只需要新增配置就可以實現擴充套件,符合開閉原則。