JAVA SPI
約定如下:當服務的提供者提供了服務接口的一種實現之后,在jar包的META-INF/services/ 目錄中同時創建一個以服務接口命名的文件,該文件中的內容就是實現該服務接口的具體實現類。
Java中提供了一個用於服務實現查找的工具類:java.util.ServiceLoader。
//將服務聲明的文件名稱定義為: example.spi.service.IService,與接口名稱一致,其中的內容包括: //example.spi.service.PrintServiceImpl //example.spi.service.EchoServiceImpl public static void main(String[] args) { //實例化具體類時需要注意對應類有無參構造函數 ServiceLoader<Service> serviceLoader = ServiceLoader.load(IService.class); for (IService service : serviceLoader) { service.printInfo(); } }
ServiceLoader的源碼分析
重要屬性:
// 加載的接口 private Class<S> service; // 用於緩存已經加載的接口實現類,其中key為實現類的完整類名 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 用於延遲加載接口的實現類 private LazyIterator lookupIterator;
第一步:獲取一個ServiceLoader<Service> serviceLoader = ServiceLoader.load(IService.class);實例,此時還沒有進行任何接口實現類的加載操作,屬於延遲加載類型的。只是創建了LazyIterator lookupIterator對象而已。
第二步:ServiceLoader實現了Iterable接口,即實現了該接口的iterator()方法,實現內容如下:
// for循環遍歷ServiceLoader的過程其實就是調用上述hasNext()和next()方法的過程 public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { if (knownProviders.hasNext()) return true; // 第一次循環遍歷會使用lookupIterator去查找,之后就緩存到providers中。
// LazyIterator會去加載類路徑下/META-INF/services/接口全稱 文件的url地址,文件加載並解析完成之后,得到一系列的接口實現類的完整類名。
// 調用next()方法時才回去真正執行接口實現類的加載操作,並根據無參構造器創建出一個實例,存到providers中。 return lookupIterator.hasNext(); } public S next() { //再次遍歷ServiceLoader,就直接遍歷providers中的數據 if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
ServiceLoader缺點
- 雖然ServiceLoader使用延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載並實例化一遍。如果你並不想用某些實現類,它也被加載並實例化了,這就造成了浪費。
- 獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類
Dubbo SPI
dubbo的擴展機制與JAVA SPI比較類似,但額外增加了其他功能:
- 可以根據接口名稱來獲取服務,dubbo spi 可以通過getExtension(String key)的方法方便的獲取某一個想要的擴展實現,java的SPI機制需要加載全部的實現類。
- 服務聲明文件支持A=B的方式,此時A為名稱B為實現類。 文件名:com.alibaba.dubbo.rpc.Filter
-
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
-
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
-
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
-
- 支持擴展IOC依賴注入功能,可以為Service之間的依賴關系注入相關的服務並保證單例。
-
舉例來說:接口A,實現者A1、A2。接口B,實現者B1、B2。
現在實現者A1含有setB()方法,會自動注入一個接口B的實現者,此時注入B1還是B2呢?
都不是,而是注入一個動態生成的接口B的實現者B$Adpative,該實現能夠根據參數的不同,自動引用B1或者B2來完成相應的功能。
Protocol$Adpative是根據URL參數中protocol屬性的值來選擇具體的實現類的。
如值為dubbo,則從ExtensionLoader<Protocol>中獲取dubbo對應的實例,即DubboProtocol實例
如值為hessian,則從ExtensionLoader<Protocol>中獲取hessian對應的實例,即HessianProtocol實例
也就是說Protocol$Adpative能夠根據url中的protocol屬性值動態的采用對應的實現。
-
-
對擴展采用裝飾器模式進行功能增強,類似AOP實現的功能
- 接口A的另一個實現者AWrapper1。在獲取某一個接口A的實現者A1的時候,已經自動被AWrapper1包裝了。
private A a; AWrapper1(A a){ this.a=a; }
- 接口A的另一個實現者AWrapper1。在獲取某一個接口A的實現者A1的時候,已經自動被AWrapper1包裝了。
ExtensionLoader源碼分析
ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class); Protocol protocol = protocolLoader.getAdaptiveExtension(); @Extension("dubbo") public interface Protocol { int getDefaultPort(); @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy(); }
第一步:根據要加載的接口創建出一個ExtensionLoader實例
重要屬性: ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
用於緩存所有的擴展加載實例,這里加載Protocol.class,就以Protocol.class為key,創建的ExtensionLoader為value存儲到上述EXTENSION_LOADERS中。
第二步:ExtensionLoader實例是加載Protocol的實現類
- 先解析Protocol上的Extension注解的name,存至String cachedDefaultName屬性中,作為默認的實現
- 到類路徑下的加載 META-INF/services/com.alibaba.dubbo.rpc.Protocol文件,然后就是讀取每一行內容,加載對應的class。(擴展配置文件: /META-INF/dubbo/internal,/META-INF/dubbo/,META-INF/services)
- 上述class分成三種情況來處理,對於一個接口的實現者,ExtensionLoader分三種情況來分別存儲對應的實現者,屬性分別如下:Class<?> cachedAdaptiveClass;Set<Class<?>> cachedWrapperClasses;Reference<Map<String, Class<?>>> cachedClasses;
- 情況1: 如果這個class含有Adaptive注解,則將這個class設置為Class<?> cachedAdaptiveClass。
- 情況2: 嘗試獲取有對應接口參數的構造器,如果能夠獲取到,則說明這個class是一個裝飾類即需要存到Set<Class<?>> cachedWrapperClasses中
- 情況3: 如果沒有上述構造器。則獲取class上的Extension注解,根據該注解的定義的name作為key,存至Reference<Map<String, Class<?>>> cachedClasses結構中
