淺析Java SPI 與 dubbo SPI
Java原生SPI
面向接口編程+策略模式
實(shí)現(xiàn)
建立接口
Robot
public interface Robot {
/**
* 測試方法1
*/
void sayHello();
}
多個(gè)實(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目錄下建立一個(gè)以接口全限定名為名字的文件,里面的內(nèi)容是實(shí)現(xiàn)類的全限定名
原理
通過ServiceLoader與配置文件中的全限定名加載所有實(shí)現(xiàn)類,根據(jù)迭代器獲取具體的某一個(gè)類
我們通過對下面一段代碼的分析來說明
ServiceLoader<Robot> serviceLoader=ServiceLoader.load(Robot.class); serviceLoader.forEach(Robot::sayHello);
load(Robot.class)這個(gè)方法的目的只是為了設(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);
}
這個(gè)load方法其實(shí)也沒有做什么實(shí)質(zhì)的事,僅僅是實(shí)例化了一個(gè)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是一個(gè)對于已實(shí)例化對象的緩存,為Map類型
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
沒有,這里僅僅只是檢驗(yàn)了參數(shù)和權(quán)限這樣一些準(zhǔn)備操作.然后實(shí)例化了一個(gè)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對象,而僅僅只是返回了一個(gè)ServiceLoader對象
這時(shí)候如果我們?nèi)タ?code>serviceLoader的對象方法是這樣的

有用的只有這三個(gè)方法,reload上面已經(jīng)提到過,只是重新實(shí)例化一個(gè)對象而已.
而另外兩個(gè)iterator()是個(gè)迭代器,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();
}
};
這個(gè)方法實(shí)際上是返回了一個(gè)Iterator對象.而通過這個(gè)Iterator,我們可以遍歷獲取我們所需要的Robot對象.
我們來看其用于獲取對象的next方法
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
這個(gè)方法是先在緩存里找,緩存里找不到,就需要用最開始的實(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),這個(gè)方法比較長,我貼一部分核心
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();
}
多個(gè)實(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目錄下建立一個(gè)以接口全限定名為名字的文件,里面的內(nèi)容是自定義名字與類的全限定名的鍵值對,舉個(gè)例子
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");
第一句代碼沒什么好說的,只是獲取一個(gè)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)建一個(gè)新的對象。
再看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í)例化一個(gè),再放入緩存中
接著用這個(gè)instance去實(shí)例化一個(gè)包裝類然后返回.自此,一個(gè)我們需要的對象產(chǎn)生了.
最后我們看看getExtensionClasses()這個(gè)方法
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就是用來存各個(gè)擴(kuò)展類Class的Map緩存,如果不存在的話,會調(diào)用loadExtensionClasses();去加載,剩下的就是找到對應(yīng)路徑下的配置文件,獲取全限定名了
上文我在分析Dubbo SPI時(shí),多次提到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的無疑更靈活,可以按需去加載某個(gè)類,也可以很便捷的通過自定義的名字去獲取類.而且Dubbo還支持setter注入.這點(diǎn)以后再講.
最后提一個(gè)問題,java原生的SPI只有在用iterator遍歷到的時(shí)候才會實(shí)例化對象,那能不能在遇到自己想要的實(shí)現(xiàn)對象時(shí)就停止遍歷,避免不必要的資源消耗呢?
補(bǔ)充:下面看下Dubbo SPI 和 Java SPI 區(qū)別?
JDK SPI
JDK 標(biāo)準(zhǔn)的 SPI 會一次性加載所有的擴(kuò)展實(shí)現(xiàn),如果有的擴(kuò)展吃實(shí)話很耗時(shí),但
也沒用上,很浪費(fèi)資源。
所以只希望加載某個(gè)的實(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 的支持,一個(gè)擴(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)文章
Spring Cloud中使用Eureka的詳細(xì)過程
Eureka 是 Netflix 開源的一個(gè)服務(wù)發(fā)現(xiàn)組件,它在微服務(wù)架構(gòu)中扮演著重要的角色,這篇文章主要介紹了Spring Cloud中如何使用Eureka,需要的朋友可以參考下2024-07-07
Java傳入用戶名和密碼并自動提交表單實(shí)現(xiàn)登錄到其他系統(tǒng)的實(shí)例代碼
這篇文章主要介紹了Java傳入用戶名和密碼并自動提交表單實(shí)現(xiàn)登錄到其他系統(tǒng),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01
java 制作驗(yàn)證碼并進(jìn)行驗(yàn)證實(shí)例詳解
這篇文章主要介紹了java 制作驗(yàn)證碼并進(jìn)行驗(yàn)證實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04
Java實(shí)現(xiàn)對中文字符串的排序功能實(shí)例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)中文字符串的排序功能實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-04-04
Java面試為何阿里強(qiáng)制要求不在foreach里執(zhí)行刪除操作
那天,小二去阿里面試,面試官老王一上來就甩給了他一道面試題:為什么阿里的 Java 開發(fā)手冊里會強(qiáng)制不要在 foreach 里進(jìn)行元素的刪除操作2021-11-11
JAVA8獲取list集合中重復(fù)的元素與獲取去重?cái)?shù)據(jù)實(shí)例
這篇文章主要給大家介紹了關(guān)于JAVA8獲取list集合中重復(fù)的元素與獲取去重?cái)?shù)據(jù)的相關(guān)資料,在實(shí)際開發(fā)中經(jīng)常會遇到需要找出(刪除)一個(gè)list中某些元素的屬性相同的元素,需要的朋友可以參考下2023-07-07
JDBC如何訪問MySQL數(shù)據(jù)庫,并增刪查改
這篇文章主要介紹了JDBC如何訪問MySQL數(shù)據(jù)庫,幫助大家更好的理解和學(xué)習(xí)java與MySQL,感興趣的朋友可以了解下2020-08-08
Java實(shí)現(xiàn)利用廣度優(yōu)先遍歷(BFS)計(jì)算最短路徑的方法
這篇文章主要介紹了Java實(shí)現(xiàn)利用廣度優(yōu)先遍歷(BFS)計(jì)算最短路徑的方法,實(shí)例分析了廣度優(yōu)先遍歷算法的原理與使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04
詳解Spring依賴注入的三種方式以及優(yōu)缺點(diǎn)
IoC?和?DI?是?Spring?中最重要的兩個(gè)概念,其中?IoC(Inversion?of?Control)為控制反轉(zhuǎn)的思想,而?DI(Dependency?Injection)依賴注入為其(IoC)具體實(shí)現(xiàn)。那么?DI?實(shí)現(xiàn)依賴注入的方式有幾種?這些注入方式又有什么不同?本文就來和大家一起詳細(xì)聊聊2022-08-08

