亚洲免费在线视频-亚洲啊v-久久免费精品视频-国产精品va-看片地址-成人在线视频网

您的位置:首頁技術(shù)文章
文章詳情頁

淺析Java SPI 與 dubbo SPI

瀏覽:7日期:2022-08-11 15:56:31
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è)類

我們通過對(duì)下面一段代碼的分析來說明

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對(duì)象返回罷了

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è)對(duì)于已實(shí)例化對(duì)象的緩存,為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對(duì)象,而僅僅只是返回了一個(gè)ServiceLoader對(duì)象

這時(shí)候如果我們?nèi)タ磗erviceLoader的對(duì)象方法是這樣的

淺析Java SPI 與 dubbo SPI

有用的只有這三個(gè)方法,reload上面已經(jīng)提到過,只是重新實(shí)例化一個(gè)對(duì)象而已.

而另外兩個(gè)iterator()是個(gè)迭代器,foreach也只是用于迭代的語法糖罷了.如果我們debug的話,會(huì)發(fā)現(xiàn)foreach的核心依舊會(huì)變成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對(duì)象.而通過這個(gè)Iterator,我們可以遍歷獲取我們所需要的Robot對(duì)象.

我們來看其用于獲取對(duì)象的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)和接口的全限定名去找到對(duì)應(yīng)的配置文件.然后加載里面的配置,獲取具體實(shí)現(xiàn)類的名字.

Dubbo增強(qiáng)SPI

實(shí)現(xiàn)

建立接口

與原生SPI不同,dubbo需要加入@SPI注解

Robot

@SPIpublic 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)容是自定義名字與類的全限定名的鍵值對(duì),舉個(gè)例子

robotA = cn.testlove.double_dubbo.inter.impl.RobotArobotB=cn.testlove.double_dubbo.inter.impl.RobotB

原理

我們通過對(duì)下列代碼的調(diào)用來進(jìn)行分析

ExtensionLoader<Robot> extensionLoader= ExtensionLoader.getExtensionLoader(Robot.class);Robot robotB = extensionLoader.getExtension('robotB');

第一句代碼沒什么好說的,只是獲取一個(gè)Robot的ExtensionLoader對(duì)象并且緩存在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è)新的對(duì)象。

再看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;

注意對(duì)于Class<?> clazz = getExtensionClasses().get(name);這一句的理解,這一句是獲取配置文件中所有類的Class實(shí)例,而不是獲取所有擴(kuò)展類的實(shí)例。

接下來的流程其實(shí)也就簡單了從EXTENSION_INSTANCES緩存中獲取instance實(shí)例,如果沒有,就借助Class對(duì)象實(shí)例化一個(gè),再放入緩存中

接著用這個(gè)instance去實(shí)例化一個(gè)包裝類然后返回.自此,一個(gè)我們需要的對(duì)象產(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緩存,如果不存在的話,會(huì)調(diào)用loadExtensionClasses();去加載,剩下的就是找到對(duì)應(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;

對(duì)比原生的java SPI,dubbo的無疑更靈活,可以按需去加載某個(gè)類,也可以很便捷的通過自定義的名字去獲取類.而且Dubbo還支持setter注入.這點(diǎn)以后再講.

最后提一個(gè)問題,java原生的SPI只有在用iterator遍歷到的時(shí)候才會(huì)實(shí)例化對(duì)象,那能不能在遇到自己想要的實(shí)現(xiàn)對(duì)象時(shí)就停止遍歷,避免不必要的資源消耗呢?

補(bǔ)充:下面看下Dubbo SPI 和 Java SPI 區(qū)別?

JDK SPI

JDK 標(biāo)準(zhǔn)的 SPI 會(huì)一次性加載所有的擴(kuò)展實(shí)現(xiàn),如果有的擴(kuò)展吃實(shí)話很耗時(shí),但

也沒用上,很浪費(fèi)資源。

所以只希望加載某個(gè)的實(shí)現(xiàn),就不現(xiàn)實(shí)了

DUBBO SPI

1,對(duì) Dubbo 進(jìn)行擴(kuò)展,不需要改動(dòng) Dubbo 的源碼

2,延遲加載,可以一次只加載自己想要加載的擴(kuò)展實(shí)現(xiàn)。

3,增加了對(duì)擴(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的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 久草欧美视频 | 美女张开腿让男人桶的动态图 | 成人精品一区二区久久 | a毛片在线播放 | 亚洲一区免费在线观看 | 成人黄色一级视频 | 香蕉视频黄在线观看 | 欧美成人精品 | 国产精品久久久久久久久久影院 | 欧美成a人免费观看久久 | 美女很黄很黄 | 亚洲国产精品一区二区首页 | 在线观看日本视频免费 | 996re免费热在线视频手机 | 明星国产欧美日韩在线观看 | 亚洲欧洲一二三区机械有限公司 | 欧美首页| 精品午夜一区二区三区在线观看 | 色偷偷亚洲精品一区 | 国产香蕉成人综合精品视频 | 综合在线播放 | 国产成人精品免费视频大全可播放的 | 性生话一级国产片 | 国产只有精品 | 欧美另类丝袜 | 国产欧美视频一区二区三区 | 伊人久久精品午夜 | 国产一级做a爱片久久毛片a | 欧美一级毛片在线播放 | 久久99国产亚洲精品观看 | 成年人看的黄色片 | 3d动漫精品成人一区二区三 | 99久久精品国产免费 | 天海翼精品久久中文字幕 | 99精品一区二区三区 | 一区二区三区 亚洲区 | 性欧美videos高清精品 | 寡妇一级毛片 | 亚洲成人美女 | 国产欧美日韩亚洲 | 99国产精品久久久久久久... |