淺析Java SPI 與 dubbo SPI
Java原生SPI
面向接口編程+策略模式
實(shí)現(xiàn)
建立接口
Robot
public interface Robot { /** * 測試方法1 */ void sayHello(); }
多個實(shí)現(xiàn)類實(shí)現(xiàn)接口
RobotA
public class RobotA implements Robot { public RobotA() { System.out.println("Happy RobotA is loaded"); } @Override public void sayHello() { System.out.println("i am a very very happy Robot "); } public void sayBye(){} }
RobotB
public class RobotB implements Robot { public RobotB() { System.out.println("SB RobotB is loaded"); } @Override public void sayHello() { System.out.println("i am a da sha bi "); } public void sayBye(){} }
配置實(shí)現(xiàn)類與接口
在META-INF/services
目錄下建立一個以接口全限定名為名字的文件,里面的內(nèi)容是實(shí)現(xiàn)類的全限定名
原理
通過ServiceLoader
與配置文件中的全限定名加載所有實(shí)現(xiàn)類,根據(jù)迭代器獲取具體的某一個類
我們通過對下面一段代碼的分析來說明
ServiceLoader<Robot> serviceLoader=ServiceLoader.load(Robot.class); serviceLoader.forEach(Robot::sayHello);
load(Robot.class)
這個方法的目的只是為了設(shè)置類加載器為線程上下文加載器,我們當(dāng)然可以不這么做,直接調(diào)用load(Class service,ClassLoader loader)
方法
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
這個load方法其實(shí)也沒有做什么實(shí)質(zhì)的事,僅僅是實(shí)例化了一個ServiceLoad對象返回罷了
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }
那是不是構(gòu)造方法做了最核心的事呢?
private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } public void reload() { //這里的provider是一個對于已實(shí)例化對象的緩存,為Map類型 providers.clear(); lookupIterator = new LazyIterator(service, loader); }
沒有,這里僅僅只是檢驗(yàn)了參數(shù)和權(quán)限這樣一些準(zhǔn)備操作.然后實(shí)例化了一個LazyIterator
這是LazyIterator
的構(gòu)造函數(shù)
private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; }
然后....,沒了,ServiceLoader<Robot> serviceLoader=ServiceLoader.load(Robot.class);
執(zhí)行完畢了,到這里,并沒有實(shí)例化我們所需要的Robot
對象,而僅僅只是返回了一個ServiceLoader
對象
這時候如果我們?nèi)タ?code>serviceLoader的對象方法是這樣的
有用的只有這三個方法,reload
上面已經(jīng)提到過,只是重新實(shí)例化一個對象而已.
而另外兩個iterator()
是個迭代器,foreach
也只是用于迭代的語法糖罷了.如果我們debug的話,會發(fā)現(xiàn)foreach
的核心依舊會變成iterator()
,好了,接下來重點(diǎn)看iterator()
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; return lookupIterator.hasNext(); } public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } };
這個方法實(shí)際上是返回了一個Iterator
對象.而通過這個Iterator
,我們可以遍歷獲取我們所需要的Robot
對象.
我們來看其用于獲取對象的next
方法
public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); }
這個方法是先在緩存里找,緩存里找不到,就需要用最開始的實(shí)例化的lookupIterator
找
再來看看它的next
方法
public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } }
這方法的核心是nextService
,我們繼續(xù)看實(shí)現(xiàn),這個方法比較長,我貼一部分核心
if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); }
用hasNextService()
判斷是否還可以繼續(xù)迭代,通過class.forName
反射獲取實(shí)例,最后再加入到provider緩存中.于是基本邏輯就完成了.那nextName
哪來的.是在hasNextService()
中獲取的.
依舊只有核心代碼
//獲取文件 String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); //解析文件配置 while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next();
根據(jù)前綴(即META-INF/services)和接口的全限定名去找到對應(yīng)的配置文件.然后加載里面的配置,獲取具體實(shí)現(xiàn)類的名字.
Dubbo增強(qiáng)SPI
實(shí)現(xiàn)
建立接口
與原生SPI不同,dubbo需要加入@SPI注解
Robot
@SPI public interface Robot { /** * 測試方法1 */ void sayHello(); }
多個實(shí)現(xiàn)類實(shí)現(xiàn)接口
RobotA
public class RobotA implements Robot { public RobotA() { System.out.println("Happy RobotA is loaded"); } @Override public void sayHello() { System.out.println("i am a very very happy Robot "); } public void sayBye(){} }
RobotB
public class RobotB implements Robot { public RobotB() { System.out.println("SB RobotB is loaded"); } @Override public void sayHello() { System.out.println("i am a da sha bi "); } public void sayBye(){} }
配置實(shí)現(xiàn)類與接口
在META-INF/dubbo
目錄下建立一個以接口全限定名為名字的文件,里面的內(nèi)容是自定義名字與類的全限定名的鍵值對,舉個例子
robotA = cn.testlove.double_dubbo.inter.impl.RobotA robotB=cn.testlove.double_dubbo.inter.impl.RobotB
原理
我們通過對下列代碼的調(diào)用來進(jìn)行分析
ExtensionLoader<Robot> extensionLoader= ExtensionLoader.getExtensionLoader(Robot.class); Robot robotB = extensionLoader.getExtension("robotB");
第一句代碼沒什么好說的,只是獲取一個Robot
的ExtensionLoader
對象并且緩存在Map中,下次如果是同樣的接口可以直接從map中獲取
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); }
再來看第二句代碼
//從緩存中找 final Holder<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); //雙重檢查 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name); holder.set(instance); } } }
首先從緩存里找,找不到再創(chuàng)建一個新的對象。
再看createExtension(name)
方法
Class<?> clazz = getExtensionClasses().get(name); 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 (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } initExtension(instance); return instance;
注意對于Class<?> clazz = getExtensionClasses().get(name);
這一句的理解,這一句是獲取配置文件中所有類的Class
實(shí)例,而不是獲取所有擴(kuò)展類的實(shí)例。
接下來的流程其實(shí)也就簡單了從EXTENSION_INSTANCES
緩存中獲取instance
實(shí)例,如果沒有,就借助Class
對象實(shí)例化一個,再放入緩存中
接著用這個instance
去實(shí)例化一個包裝類然后返回.自此,一個我們需要的對象產(chǎn)生了.
最后我們看看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;
這里的classes
就是用來存各個擴(kuò)展類Class
的Map緩存,如果不存在的話,會調(diào)用loadExtensionClasses();
去加載,剩下的就是找到對應(yīng)路徑下的配置文件,獲取全限定名了
上文我在分析Dubbo SPI時,多次提到Map,緩存二詞,我們可以具體有以下這些.其實(shí)看名字就大概知道作用了
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>(); private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>() private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>(); private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>(); private final Holder<Object> cachedAdaptiveInstance = new Holder<>(); private volatile Class<?> cachedAdaptiveClass = null; private String cachedDefaultName;
對比原生的java SPI,dubbo的無疑更靈活,可以按需去加載某個類,也可以很便捷的通過自定義的名字去獲取類.而且Dubbo還支持setter注入.這點(diǎn)以后再講.
最后提一個問題,java原生的SPI只有在用iterator
遍歷到的時候才會實(shí)例化對象,那能不能在遇到自己想要的實(shí)現(xiàn)對象時就停止遍歷,避免不必要的資源消耗呢?
補(bǔ)充:下面看下Dubbo SPI 和 Java SPI 區(qū)別?
JDK SPI
JDK 標(biāo)準(zhǔn)的 SPI 會一次性加載所有的擴(kuò)展實(shí)現(xiàn),如果有的擴(kuò)展吃實(shí)話很耗時,但
也沒用上,很浪費(fèi)資源。
所以只希望加載某個的實(shí)現(xiàn),就不現(xiàn)實(shí)了
DUBBO SPI
1,對 Dubbo 進(jìn)行擴(kuò)展,不需要改動 Dubbo 的源碼
2,延遲加載,可以一次只加載自己想要加載的擴(kuò)展實(shí)現(xiàn)。
3,增加了對擴(kuò)展點(diǎn) IOC 和 AOP 的支持,一個擴(kuò)展點(diǎn)可以直接 setter 注入其它擴(kuò)展點(diǎn)。
3,Dubbo 的擴(kuò)展機(jī)制能很好的支持第三方 IoC 容器,默認(rèn)支持 Spring Bean。
以上就是Java SPI 與 dubbo SPI的詳細(xì)內(nèi)容,更多關(guān)于Java SPI 與 dubbo SPI的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java concurrency之鎖_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java concurrency之鎖的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06關(guān)于Assert.assertEquals報錯的問題及解決
這篇文章主要介紹了關(guān)于Assert.assertEquals報錯的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05idea上提交項(xiàng)目到gitee 最后出現(xiàn) Push rejected的問題處理方法
這篇文章主要介紹了idea上面提交項(xiàng)目到gitee 最后出現(xiàn) Push rejected的問題處理方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定參考借鑒價值,需要的朋友可以參考下2020-09-09IntelliJ IDEA基于Maven構(gòu)建Java項(xiàng)目
在 Java 開發(fā)中,使用 Maven 是一種廣泛采用的構(gòu)建工具,本文主要介紹了IntelliJ IDEA基于Maven構(gòu)建Java項(xiàng)目,具有一定的參考價值,感興趣的可以了解一下2024-03-03Eclipse項(xiàng)目怎么導(dǎo)入IDEA并運(yùn)行(超詳細(xì))
這篇文章主要介紹了Eclipse項(xiàng)目怎么導(dǎo)入IDEA并運(yùn)行(超詳細(xì)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10SpringBoot 圖書管理系統(tǒng)(刪除、強(qiáng)制登錄、更新圖書)詳細(xì)代碼
在企業(yè)開發(fā)中,通常不采用delete語句進(jìn)行物理刪除,而是使用邏輯刪除,邏輯刪除通過修改標(biāo)識字段來表示數(shù)據(jù)已被刪除,方便數(shù)據(jù)恢復(fù),本文給大家介紹SpringBoot 圖書管理系統(tǒng)實(shí)例代碼,感興趣的朋友跟隨小編一起看看吧2024-09-09