Java 反射中,Class.forName和ClassLoader的區別

一、區別

java 類裝載過程分爲 3 步:

加載 --> 鏈接 (1、校驗, 2、準備, 3、解析) --> 初始化

1、加載 Jvm 把 class 文件字節碼加載到內存中,並將這些靜態數據裝換成運行時數據區中方法區的類型數據,在運行時數據區堆中生成一個代表這個類的 java.lang.Class 對象,作爲方法區類數據的訪問入口。

  • 注:方法區不僅僅是存放方法,它存放的是類的類型信息。

2、鏈接:執行下面的校驗、準備和解析步驟,其中解析步驟是可選的 a:校驗:檢查加載的 class 文件的正確性和安全性 b:準備:爲類變量分配存儲空間並設置類變量初始值,類變量隨類型信息存放在方法區中, 生命週期很長,使用不當和容易造成內存泄漏。

  • 注:類變量就是 static 變量;初始值指的是類變量類型的默認值而不是實際要賦的值
  • c:解析:jvm 將常量池內的符號引用轉換爲直接引用

3、初始化:執行類變量賦值和靜態代碼塊

在瞭解了類裝載過程之後我們繼續比較二者區別:Classloder.loaderClass(String name) 其實該方法內部調用的是:Classloder. loadClass(name, false) 方法:Classloder. loadClass(String name, boolean resolve) a:參數 name 代表類的全限定類名 b:參數 resolve 代表是否解析,resolve 爲 true 是解析該類

Class.forName(String name) 其實該方法內部調用的是:Class.forName(className, true, ClassLoader.getClassLoader(caller)) 方法:Class.forName0(String name, boolean initialize, ClassLoader loader)

參數 name 代表全限定類名

參數 initialize 表示是否初始化該類,爲 true 是初始化該類

參數 loader 對應的類加載器

兩者最大的區別 Class.forName 得到的 class 是已經初始化完成的 Classloder.loaderClass 得到的 class 是還沒有鏈接的

二、代碼演示

java 中 class.forName()classLoader 都可用來對類進行加載。 class.forName() 前者除了將類的. class 文件加載到 jvm 中之外,還會對類進行解釋,執行類中的 static 塊。 而 classLoader 只幹一件事情,就是將. class 文件加載到 jvm 中,不會執行 static 中的內容, 只有在 newInstance 纔會去執行 static 塊。 Class.forName(name, initialize, loader) 帶參函數也可控制是否加載 static 塊。並且只有調用了 newInstance() 方法採用調用構造函數,創建類的對象。

看下 Class.forName() 源碼:

// Class.forName(String className)  這是1.8的源碼
public static Class forName(String className) throws ClassNotFoundException {
    Class caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
// 注意第二個參數,是指Class被loading後是不是必須被初始化。 不初始化就是不執行static的代碼即靜態代碼

接着測試代碼證明上面的結論是 OK 的,如下:

package com.reflect;

public class Line {
    static {
        System.out.println("靜態代碼塊執行: loading line");
    }
}
package com.reflect;

public class Point {
    static {
        System.out.println("靜態代碼塊執行: loading point");
    }
}
package com.reflect;

public class ClassloaderAndForNameTest {
    public static void main(String[] args) {
        String wholeNameLine = "com.reflect.Line";
        String wholeNamePoint = "com.reflect.Point";
        System.out.println("下面是測試Classloader的效果");
        testClassloader(wholeNameLine, wholeNamePoint);
        System.out.println("----------------------------------");
        System.out.println("下面是測試Class.forName的效果");
        testForName(wholeNameLine, wholeNamePoint);
    }

    /**
     * classloader
     * @param wholeNameLine
     * @param wholeNamePoint
     */
    private static void testClassloader(String wholeNameLine, String wholeNamePoint) {
        Class line;
        Class point;
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        try {
            line = loader.loadClass(wholeNameLine);
            point = loader.loadClass(wholeNamePoint);
            System.out.println("line " + line.getName());
            System.out.println("point " + point.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * Class.forName
     * @param wholeNameLine
     * @param wholeNamePoint
     */
    private static void testForName(String wholeNameLine, String wholeNamePoint) {
        try {
            Class line = Class.forName(wholeNameLine);
            Class point = Class.forName(wholeNamePoint);
            System.out.println("line   " + line.getName());
            System.out.println("point   " + point.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

輸出結果:

下面是測試Classloader的效果
line com.reflect.Line
point com.reflect.Point
----------------------------------
下面是測試Class.forName的效果
靜態代碼塊執行: loading line
靜態代碼塊執行: loading point
line   com.reflect.Line
point   com.reflect.Point

備註:

根據運行結果,可以看到,classloader 並沒有執行靜態代碼塊,如開頭的理論所說。

而下面的 Class.forName 則是夾在完之後,就裏面執行了靜態代碼塊,可以看到,2 個類,line 和 point 的靜態代碼塊執行結果是一起的,然後纔是各自的打印結果。

也說明上面理論是 OK 的。

接下來修改下 Line 的代碼,添加了幾個靜態的方法和變量。

package com.reflect;

public class Line {
    static {
        System.out.println("靜態代碼塊執行: loading line");
    }

    public static String s = getString();

    private static String getString() {
        System.out.println("給靜態變量賦值的靜態方法執行:loading line");
        return "mask";
    }

    public static void test() {
        System.out.println("普通靜態方法執行:loading line");
    }

    {
        System.out.println("普通代碼塊");
    }

    public Line() {
        System.out.println("構造方法執行");
    }

}

可以看到,除了原來的簡單的一個靜態代碼塊以外,我又添加了構造方法,靜態方法,以及靜態變量,且,靜態變量被一個靜態方法賦值。

然後,看執行結果。

下面是測試Classloader的效果
line com.reflect.Line
point com.reflect.Point
----------------------------------
下面是測試Class.forName的效果
靜態代碼塊執行: loading line
給靜態變量賦值的靜態方法執行:loading line
靜態代碼塊執行: loading point
line   com.reflect.Line
point   com.reflect.Point

除了,靜態代碼塊的執行外,竟然還有一個靜態方法被執行,就是給靜態變量賦值的靜態方法被執行了。

三、應用場景

在我們熟悉的 Spring 框架中的 IOC 的實現就是使用的 ClassLoader。

而在我們使用 JDBC 時通常是使用 Class.forName() 方法來加載數據庫連接驅動。這是因爲在 JDBC 規範中明確要求 Driver(數據庫驅動) 類必須向 DriverManager 註冊自己

以 MySQL 的驅動爲例解釋:

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 中的操作寫在了靜態代碼塊中,這就是爲什麼在寫 JDBC 時使用 Class.forName() 的原因了。

作者:老周聊架構

來源:稀土掘金

鏈接:https://juejin.cn/post/6952750364936372237

版權聲明: 內容來源網絡,僅供分享學習,版權歸原創作者所有,除非無法確認,我們都會標明作者及出處,如有侵權煩請告知,我們會立即刪除表示歉意。謝謝!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。