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

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

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



