Class.forName 和 ClassLoader 到底有啥區(qū)別?

作者 |?紀(jì)莫
前言
最近在面試過程中有被問到,在Java反射中Class.forName()加載類和使用ClassLoader加載類的區(qū)別。當(dāng)時沒有想出來后來自己研究了一下就寫下來記錄一下。
解釋
在java中Class.forName()和ClassLoader都可以對類進(jìn)行加載。ClassLoader就是遵循雙親委派模型最終調(diào)用啟動類加載器的類加載器,實現(xiàn)的功能是“通過一個類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流”,獲取到二進(jìn)制流后放到JVM中。Class.forName()方法實際上也是調(diào)用的ClassLoader來實現(xiàn)的。
Class.forName(String className);這個方法的源碼是
@CallerSensitive
public?static?Class>?forName(String?className)
????????????throws?ClassNotFoundException?{
????Class>?caller?=?Reflection.getCallerClass();
????return?forName0(className,?true,?ClassLoader.getClassLoader(caller),?caller);
}
最后調(diào)用的方法是forName0這個方法,在這個forName0方法中的第二個參數(shù)被默認(rèn)設(shè)置為了true,這個參數(shù)代表是否對加載的類進(jìn)行初始化,設(shè)置為true時會類進(jìn)行初始化,代表會執(zhí)行類中的靜態(tài)代碼塊,以及對靜態(tài)變量的賦值等操作。
也可以調(diào)用Class.forName(String name, boolean initialize,ClassLoader loader)方法來手動選擇在加載類的時候是否要對類進(jìn)行初始化。Class.forName(String name, boolean initialize,ClassLoader loader)的源碼如下:
/*?@param?name???????fully?qualified?name?of?the?desired?class
?*?@param?initialize?if?{@code?true}?the?class?will?be?initialized.
?*???????????????????See?Section?12.4?of?The?Java?Language?Specification.
?*?@param?loader?????class?loader?from?which?the?class?must?be?loaded
?*?@return???????????class?object?representing?the?desired?class
?*
?*?@exception?LinkageError?if?the?linkage?fails
?*?@exception?ExceptionInInitializerError?if?the?initialization?provoked
?*????????????by?this?method?fails
?*?@exception?ClassNotFoundException?if?the?class?cannot?be?located?by
?*????????????the?specified?class?loader
?*
?*?@see???????java.lang.Class#forName(String)
?*?@see???????java.lang.ClassLoader
?*?@since?????1.2?????*/
@CallerSensitive
public?static?Class>?forName(String?name,?boolean?initialize,
???????????????????????????????ClassLoader?loader)
????throws?ClassNotFoundException
{
????Class>?caller?=?null;
????SecurityManager?sm?=?System.getSecurityManager();
????if?(sm?!=?null)?{
????????//?Reflective?call?to?get?caller?class?is?only?needed?if?a?security?manager
????????//?is?present.??Avoid?the?overhead?of?making?this?call?otherwise.
????????caller?=?Reflection.getCallerClass();
????????if?(sun.misc.VM.isSystemDomainLoader(loader))?{
????????????ClassLoader?ccl?=?ClassLoader.getClassLoader(caller);
????????????if?(!sun.misc.VM.isSystemDomainLoader(ccl))?{
????????????????sm.checkPermission(
????????????????????SecurityConstants.GET\_CLASSLOADER\_PERMISSION);
????????????}
????????}
????}
????return?forName0(name,?initialize,?loader,?caller);
}
源碼中的注釋只摘取了一部分,其中對參數(shù)initialize的描述是:if {@code true} the class will be initialized.意思就是說:如果參數(shù)為true,則加載的類將會被初始化。
舉例
下面還是舉例來說明結(jié)果吧:
一個含有靜態(tài)代碼塊、靜態(tài)變量、賦值給靜態(tài)變量的靜態(tài)方法的類
public?class?ClassForName?{
????//靜態(tài)代碼塊
????static?{
????????System.out.println("執(zhí)行了靜態(tài)代碼塊");
????}
????//靜態(tài)變量
????private?static?String?staticFiled?=?staticMethod();
????//賦值靜態(tài)變量的靜態(tài)方法
????public?static?String?staticMethod(){
????????System.out.println("執(zhí)行了靜態(tài)方法");
????????return?"給靜態(tài)字段賦值了";
????}
}
使用Class.forName()的測試方法:
@Test
public?void?test45(){
????try?{
????????ClassLoader.getSystemClassLoader().loadClass("com.eurekaclient2.client2.ClassForName");
????????System.out.println("#########-------------結(jié)束符------------##########");
????}?catch?(ClassNotFoundException?e)?{
????????e.printStackTrace();
????}
}
運行結(jié)果:
執(zhí)行了靜態(tài)代碼塊執(zhí)行了靜態(tài)方法#########-------------結(jié)束符------------##########??
使用ClassLoader的測試方法:
@Test
public?void?test45(){
????try?{
????????ClassLoader.getSystemClassLoader().loadClass("com.eurekaclient2.client2.ClassForName");
????????System.out.println("#########-------------結(jié)束符------------##########");
????}?catch?(ClassNotFoundException?e)?{
????????e.printStackTrace();
????}
}
運行結(jié)果:
#########-------------結(jié)束符------------##########
根據(jù)運行結(jié)果得出Class.forName加載類時將類進(jìn)了初始化,而ClassLoader的loadClass并沒有對類進(jìn)行初始化,只是把類加載到了虛擬機中。
應(yīng)用場景
在我們熟悉的Spring框架中的IOC的實現(xiàn)就是使用的ClassLoader。
而在我們使用JDBC時通常是使用Class.forName()方法來加載數(shù)據(jù)庫連接驅(qū)動。這是因為在JDBC規(guī)范中明確要求Driver(數(shù)據(jù)庫驅(qū)動)類必須向DriverManager注冊自己。
以MySQL的驅(qū)動為例解釋:
public?class?Driver?extends?NonRegisteringDriver?implements?java.sql.Driver?{??
????//?~?Static?fields/initializers??
????//?---------------------------------------------??
????//??
????//?Register?ourselves?with?the?DriverManager??
????//??
????static?{??
????????try?{??
????????????java.sql.DriverManager.registerDriver(new?Driver());??
????????}?catch?(SQLException?E)?{??
????????????throw?new?RuntimeException("Can't?register?driver!");??
????????}??
????}??
????//?~?Constructors??
????//?-----------------------------------------------------------??
????/**?
?????*?Construct?a?new?driver?and?register?it?with?DriverManager?
?????*??
?????*?@throws?SQLException?
?????*?????????????if?a?database?error?occurs.?
?????*/??
????public?Driver()?throws?SQLException?{??
????????//?Required?for?Class.forName().newInstance()??????}??
}
我們看到Driver注冊到DriverManager中的操作寫在了靜態(tài)代碼塊中,這就是為什么在寫JDBC時使用Class.forName()的原因了。關(guān)注微信公眾號:Java技術(shù)棧,在后臺回復(fù):java,可以獲取我整理的 N 篇最新 Java?教程,都是干貨。
好了,今天就寫到這了,最近在面試,遇到了很多問題,也學(xué)習(xí)了不少,雖然很累,但是也讓人成長了不少,畢竟面試就是一個脫皮的過程,會遇到各種企業(yè)各種面試官各種問題,各種場景。
給自己加油吧,找一個最少能讓自己干個幾年的公司,別總是讓我遇到工作了沒多久公司就垮掉的這種就行了。要不我也很無奈啊。
等找到工作后,會總結(jié)自己面經(jīng)的。
- 推薦閱讀 -
《架構(gòu)師離職后,成為自由開發(fā)者的第 100 天》
下方二維碼關(guān)注我

互聯(lián)網(wǎng)草根,堅持分享技術(shù)、創(chuàng)業(yè)、產(chǎn)品等心得和總結(jié)~

點擊“閱讀原文”,領(lǐng)取 2020 年最新免費技術(shù)資料大全
