《跟二師兄學(xué)Nacos吧》EXT-03篇 Nacos中此處為什么采用反射機(jī)制?
學(xué)習(xí)不用那么功利,二師兄帶你從更高維度輕松閱讀源碼~
大家可能看到過很多寫Java反射機(jī)制的文章,但如果在閱讀源碼的過程中,遇到反射機(jī)制的使用,你是否想過為什么要這么用嗎?
這篇文章就帶大家來看看Nacos中對(duì)Java反射機(jī)制的一處實(shí)踐案例。這篇文章既屬于知識(shí)點(diǎn)的分析,也屬于Nacos設(shè)計(jì)層面的分析。
Nacos中反射機(jī)制實(shí)踐
先來介紹一下Nacos反射機(jī)制使用的背景。
nacos-client項(xiàng)目中,可以通過NacosFactory獲得NamingService,然后基于NamingService來進(jìn)行服務(wù)實(shí)例的注冊(cè)功能:
NamingService namingService = NacosFactory.createNamingService(properties);
namingService.registerInstance("nacos.test.1", instance);
而在NacosFactory中又是基于NamingFactory來實(shí)現(xiàn)NamingService的創(chuàng)建的:
public static NamingService createNamingService(Properties properties) throws NacosException {
return NamingFactory.createNamingService(properties);
}
NamingFactory具體創(chuàng)建部分代碼如下:
public static NamingService createNamingService(Properties properties) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
return (NamingService) constructor.newInstance(properties);
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
到這里,終于看到了反射機(jī)制的使用了,通過Class#forName方法獲取Class對(duì)象,然后獲取構(gòu)造方法,創(chuàng)建實(shí)例。
如果你閱讀源碼時(shí)只看到這些,可能你會(huì)錯(cuò)過一些有意思的設(shè)計(jì)和事情。你是否思考過,為什么這里要采用反射機(jī)制呢?直接new一個(gè)對(duì)象不行嗎?
在解答上述問題之前,我們先來簡(jiǎn)單科普一下Java發(fā)反射機(jī)制。
Java反射機(jī)制
這里從基本概念、原理、簡(jiǎn)單實(shí)踐說起。
Java反射簡(jiǎn)介
Java是預(yù)編的語(yǔ)言,對(duì)象的類型在編譯期已經(jīng)確定。在程序運(yùn)行時(shí)可能需要?jiǎng)討B(tài)加載某些類,這些類之前用不到,所以就沒有被加載到JVM中。需要時(shí),可通過反射在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建對(duì)象并調(diào)用其屬性或方法,而不需要在編譯期就知道運(yùn)行的對(duì)象是誰(shuí)。
Java反射機(jī)制的核心是在程序運(yùn)行時(shí)動(dòng)態(tài)加載類并獲取類的詳細(xì)信息,從而能夠操作類或?qū)ο蟮膶傩院头椒ā?/p>
Java反射的優(yōu)缺點(diǎn)
Java反射的優(yōu)點(diǎn):
增加程序的靈活性,避免將程序?qū)懰赖酱a里; 代碼簡(jiǎn)潔,提高代碼的復(fù)用率,外部調(diào)用方便; 對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法;
反射的原理
在了解反射的基本原理之前,我們需要知道在Java程序編譯完成之后,會(huì)把所有class文件中所包含的類的基本元信息裝載到JVM內(nèi)存中,以Class類的形式保存。Class類可理解為描述類的類,每一個(gè)Class類對(duì)象代表一個(gè)具體類的基本元信息。反射就是在Class類的基礎(chǔ)上進(jìn)行的,Class類對(duì)象存儲(chǔ)著類的所有相關(guān)信息。

關(guān)于JVM內(nèi)部的操作步驟,我們這里不做拓展。需要了解的就是Class對(duì)象是JVM加載.class文件之后生成的對(duì)象,而反射機(jī)制提供了獲取該對(duì)象,可以基于此進(jìn)行屬性訪問或?qū)ο髽?gòu)造。而這一步是發(fā)生在運(yùn)行時(shí)期間。
反射的基本使用
通常使用反射有三種方式:
// 方式一:使用Class.forName靜態(tài)方法
Class clz = Class.forName("java.lang.String");
// 方式二:使用.class方法
Class clz = String.class;
// 方式三:使用類對(duì)象的getClass()方法
String str = new String("Hello");
Class clz = str.getClass();
上述三種方式,一般常用第一種,字符串參數(shù)可以傳入也可以寫在配置文件中。第二種需要導(dǎo)入類包,依賴太強(qiáng),不導(dǎo)包就拋編譯錯(cuò)誤。第三種對(duì)象都有了還要反射干什么。
所以說,通常我們學(xué)習(xí)的時(shí)候,知道了很多種方式,而真正使用時(shí),還是需要根據(jù)場(chǎng)景進(jìn)行選擇。而Nacos的源碼中就采用了第一種的方式。
關(guān)于反射的其他API的使用此處就不再展開了,下面回到主題,來思考一下Nacos為什么使用反射,同時(shí)為什么采用第一種方式。
Nacos反射機(jī)制原理分析
在分析之前,我們先來看一下項(xiàng)目結(jié)構(gòu)。首先,nacos-client項(xiàng)目依賴于nacos-aip項(xiàng)目,NamingFactory和NamingService位于nacos-api項(xiàng)目當(dāng)中。而具體被實(shí)例化的對(duì)象類com.alibaba.nacos.client.naming.NacosNamingService,很明顯位于nacos-client當(dāng)中。

通過上圖我們可以看到,NamingFactory中實(shí)現(xiàn)了NamingService的實(shí)例化業(yè)務(wù)邏輯,但此時(shí)nacos-api項(xiàng)目并沒有NacoNamingService,也就無法采用上面提到的其他兩種方法,只能通過Class.forName方式來進(jìn)行實(shí)現(xiàn)了。
其實(shí)這里的設(shè)計(jì)與數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序類似,nacos-api中通過NamingService定義了一個(gè)接口,也就是定義了一個(gè)標(biāo)準(zhǔn)。而nacos-client中實(shí)現(xiàn)了這個(gè)標(biāo)準(zhǔn),并且還要滿足兩個(gè)條件:第一,該實(shí)現(xiàn)類實(shí)現(xiàn)自NamingService;第二,該類的全路徑名要與NamingFactory中的實(shí)例化對(duì)象時(shí)的名稱一樣。
回頭再仔細(xì)想象,Nacos的用法,也不正是反射機(jī)制很典型的使用場(chǎng)景之一嗎?
小結(jié)
本文從Nacos的反射機(jī)制出發(fā),深入思考提出問題,并簡(jiǎn)單介紹了Java的反射機(jī)制。最終進(jìn)一步分析Nacos項(xiàng)目結(jié)構(gòu),解答了最開始的疑問。你會(huì)如此閱讀源代碼嗎?你學(xué)到了嗎?趕緊關(guān)注上車!后續(xù)更多干貨輸出。
如果文章內(nèi)容有問題或想技術(shù)討論請(qǐng)聯(lián)系我(微信:zhuan2quan,備注Nacos),如果覺得寫的還不錯(cuò),值得一起學(xué)習(xí),那就關(guān)注一下吧。
往期推薦
如果你覺得這篇文章不錯(cuò),那么,下篇通常會(huì)更好。添加微信好友,可備注“加群”(微信號(hào):zhuan2quan)。
和花一輩子都看不清的人,
注定是截然不同的搬磚生涯。



