java反射機(jī)制與類加載機(jī)制

java反射機(jī)制與類加載機(jī)制

Class (Java SE 9 & JDK 9 ) - https://docs.oracle.com/javase/9/docs/api/java/lang/Class.html
Field (Java Platform SE 7 ) - https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Field.html
ByteArrayOutputStream (Java SE 9 & JDK 9 ) - https://docs.oracle.com/javase/9/docs/api/java/io/ByteArrayOutputStream.html
URL (Java SE 9 & JDK 9 ) - https://docs.oracle.com/javase/9/docs/api/java/net/URL.html
Class熱替換與卸載 - http://www.importnew.com/22462.html
深入探討 Java 類加載器 - https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html
反射機(jī)制(Reflection) – http://blog.qiji.tech/archives/4374
深入理解Java類型信息(Class對象)與反射機(jī)制 - https://blog.csdn.net/javazejian/article/details/70768369
深入理解Java類加載器(一):Java類加載原理解析 - https://blog.csdn.net/justloveyou_/article/details/72217806

認(rèn)識Class對象之前,先來了解一個(gè)概念,RTTI(Run-Time Type Identification)運(yùn)行時(shí)類型識別,對于這個(gè)詞一直是 C++ 中的概念,至于Java中出現(xiàn)RRTI的說法則是源于《Thinking in Java》一書,其作用是在運(yùn)行時(shí)識別一個(gè)對象的類型和類的信息,這里分兩種:傳統(tǒng)的 RTTI,它假定我們在編譯期已知道了所有類型,在沒有反射機(jī)制創(chuàng)建和使用類對象時(shí),一般都是編譯期已確定其類型,如new對象時(shí)該類必須已定義好。另外一種是反射機(jī)制,它允許我們在運(yùn)行時(shí)發(fā)現(xiàn)和使用類型的信息。在Java中用來表示運(yùn)行時(shí)類型信息的對應(yīng)類就是Class類,Class類也是一個(gè)實(shí)實(shí)在在的類,存在于JDK的java.lang包中。

Class類也是類的一種,但是特別地 Class 類用來描述使用 class 關(guān)鍵字定義的類,這些描述信息用于在運(yùn)行時(shí)獲取類定義內(nèi)容,Class類的對象作用是運(yùn)行時(shí)提供或獲得某個(gè)對象的類型信息。編寫好的類在編譯后都會產(chǎn)生一個(gè)Class對象,表示的是創(chuàng)建的類的類型信息,而且這個(gè)Class對象保存在同名.class的字節(jié)碼文件中。比如創(chuàng)建一個(gè)Shapes類,編譯Shapes類后就會創(chuàng)建其包含Shapes類相關(guān)類型信息的Class對象,并保存在Shapes.class字節(jié)碼文件中。每個(gè)通過關(guān)鍵字class標(biāo)識的類,在內(nèi)存中有且只有一個(gè)與之對應(yīng)的Class對象來描述其類型信息,無論創(chuàng)建多少個(gè)實(shí)例對象,其依據(jù)的都是用一個(gè)Class對象。Class類只存私有構(gòu)造函數(shù),因此對應(yīng)Class對象只能有JVM創(chuàng)建和加載。

在運(yùn)行期間,如果我們要產(chǎn)生某個(gè)類的對象,Java虛擬機(jī)(JVM)會檢查該類型的Class對象是否已被加載。如果沒有被加載,JVM會根據(jù)類的名稱找到.class文件并加載它。一旦某個(gè)類型的Class對象已被加載到內(nèi)存,就可以用它來產(chǎn)生該類型的所有對象。

通過反射方法實(shí)例化對象及修改私有成員為公有成員

import java.lang.reflect.*;

class Gum {
    private int weight = 0;
    static { System.out.println("Loading Gum"); }
    public Gum(){ }
    public Gum(int i){ weight = i; }
    public String greeting(){ return "Hi!"; }
}

public class coding {
    public static void print(Object obj) {
        System.out.println(obj);
    }
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        try {
            Class clz = Class.forName("Gum");
            // Class clazz = Gum.class;
            // Constructor[] cts = clz.getConstructors();
            // for ( Constructor c:cts ) {
            //  print("Contructor: "+c.getName?());
            // }
            // Constructor constructor = clz.getConstructor();
            // Object object = clz.newInstance(); 
            Constructor constructor = clz.getConstructor(int.class);
            Object object = constructor.newInstance(0);

            Method method = clz.getMethod("greeting");
            String msg = (String)method.invoke(object);
            print(msg);

            Field field = clz.getDeclaredField("weight");
            field.setAccessible(true);
            print("get private member:"+field.get(object));

            Class<?> class1 = Class.forName("Gum");
            String name = class1.getClassLoader().getClass().getName();
            print("class loader is " + name);

        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

使用類反射方法 Class.forName() 載入類,返回值是對應(yīng)類的Class對象,也可以通過對象實(shí)例的 getClass() 方法獲取,這是基礎(chǔ)對象類Object定義的方法,字面常量的方式獲取Class對象如 Object.class。如果沒有定義相應(yīng)的類構(gòu)造函數(shù),getConstructor() 會引發(fā) NoSuchMethod 異常。

Field.setAccessible(true) 并不是將方法的訪問權(quán)限改成了public,而是取消java的權(quán)限控制檢查。即使是public成員,其accessible 屬性默認(rèn)也是false,即需要進(jìn)行權(quán)限檢查。

與Java反射相關(guān)的類如下:

Class類         代表類的實(shí)體,在運(yùn)行的Java應(yīng)用程序中表示類和接口
Field類         代表類的成員變量(成員變量也稱為類的屬性)
Method類        代表類的方法
Constructor類   代表類的構(gòu)造方法

類加載器

JVM預(yù)定義幾種類加載器,當(dāng)JVM啟動的時(shí)候,Java缺省開始使用如下三種類型的類加載器:

啟動(Bootstrap)類加載器:引導(dǎo)類加載器是用 本地代碼實(shí)現(xiàn)的類加載器,它負(fù)責(zé)將 <JAVA_HOME>/lib下面的核心類庫 或 -Xbootclasspath選項(xiàng)指定的jar包等 虛擬機(jī)識別的類庫 加載到內(nèi)存中。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開發(fā)者無法直接獲取到啟動類加載器的引用,所以 不允許直接通過引用進(jìn)行操作。

擴(kuò)展(Extension)類加載器:擴(kuò)展類加載器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的,它負(fù)責(zé)將 <JAVA_HOME >/lib/ext或者由系統(tǒng)變量-Djava.ext.dir指定位置中的類庫 加載到內(nèi)存中。開發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。

系統(tǒng)(System)類加載器:系統(tǒng)類加載器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的,它負(fù)責(zé)將 用戶類路徑(java -classpath或-Djava.class.path變量所指的目錄,即當(dāng)前類所在路徑及其引用的第三方類庫的路徑,如第四節(jié)中的問題6所述)下的類庫 加載到內(nèi)存中。開發(fā)者可以直接使用系統(tǒng)類加載器,是最常用的加載器,同時(shí)也是java中默認(rèn)的加載器。通過Java反射機(jī)制 Class.getClassLoader() 可以得到類加載器信息。

除了以上列舉的三種類加載器,還有一種比較特殊的類型就是線程上下文類加載器(context class loader),從 JDK 1.2 開始引入的。Java.lang.Thread 類中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用來獲取和設(shè)置線程的上下文類加載器。如果沒有通過 setContextClassLoader(ClassLoader cl)方法進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。Java 應(yīng)用運(yùn)行的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運(yùn)行的代碼可以通過此類加載器來加載類和資源。使用線程上下文類加載器,可以在執(zhí)行線程中拋棄雙親委派加載鏈模式,使用線程上下文里的類加載器加載類。典型的例子有:通過線程上下文來加載第三方庫jndi實(shí)現(xiàn),而不依賴于雙親委派。大部分Java application服務(wù)器(jboss, tomcat..)也是采用contextClassLoader來處理web服務(wù)。還有一些采用hot swap特性的框架,也使用了線程上下文類加載器,比如 seasar (full stack framework in japenese)。

JVM在加載類時(shí)默認(rèn)采用的是雙親委派機(jī)制。通俗的講,就是某個(gè)特定的類加載器在接到加載類的請求時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸 (本質(zhì)上就是loadClass函數(shù)的遞歸調(diào)用)。因此,所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中。如果父類加載器可以完成這個(gè)類加載請求,就成功返回;只有當(dāng)父類加載器無法完成此加載請求時(shí),子加載器才會嘗試自己去加載。事實(shí)上,大多數(shù)情況下,越基礎(chǔ)的類由越上層的加載器進(jìn)行加載,因?yàn)檫@些基礎(chǔ)類之所以稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a調(diào)用的API(當(dāng)然,也存在基礎(chǔ)類回調(diào)用戶用戶代碼的情形)。 系統(tǒng)類加載器的父類加載器是標(biāo)準(zhǔn)擴(kuò)展類加載器,標(biāo)準(zhǔn)擴(kuò)展類加載器的父類加載器是啟動類加載器。

虛擬機(jī)出于安全等因素考慮,不會加載<JAVA_HOME>/lib目錄下存在的陌生類,換句話說,虛擬機(jī)只加載<JAVA_HOME>/lib目錄下它可以識別的類。因此,開發(fā)者通過將要加載的非JDK自身的類放置到此目錄下期待啟動類加載器加載是不可能的。

在絕大多數(shù)情況下,系統(tǒng)默認(rèn)提供的類加載器實(shí)現(xiàn)已經(jīng)可以滿足需求。但是在某些情況下,您還是需要為應(yīng)用開發(fā)出自己的類加載器。比如您的應(yīng)用通過網(wǎng)絡(luò)來傳輸Java類的字節(jié)代碼,為了保證安全性,這些字節(jié)代碼經(jīng)過了加密處理。這個(gè)時(shí)候您就需要自己的類加載器來從某個(gè)網(wǎng)絡(luò)地址上讀取加密后的字節(jié)代碼,接著進(jìn)行解密和驗(yàn)證,最后定義出要在Java虛擬機(jī)中運(yùn)行的類來。下面將通過兩個(gè)具體的實(shí)例來說明類加載器的開發(fā)。通過用戶自定義的類裝載器,你的程序可以加載在編譯時(shí)并不知道或者尚未存在的類或者接口,并動態(tài)連接它們并進(jìn)行有選擇的解析。

除了和本地實(shí)現(xiàn)密切相關(guān)的啟動類加載器之外,包括標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器在內(nèi)的所有其他類加載器我們都可以當(dāng)做自定義類加載器來對待,唯一區(qū)別是是否被虛擬機(jī)默認(rèn)使用。前面的內(nèi)容中已經(jīng)對java.lang.ClassLoader抽象類中的幾個(gè)重要的方法做了介紹,這里就簡要敘述一下一般 用戶自定義類加載器的工作流程(可以結(jié)合后面問題解答一起看):

1、首先檢查請求的類型是否已經(jīng)被這個(gè)類裝載器裝載到命名空間中了,如果已經(jīng)裝載,直接返回;否則轉(zhuǎn)入步驟2;

2、委派類加載請求給父類加載器(更準(zhǔn)確的說應(yīng)該是雙親類加載器,真實(shí)虛擬機(jī)中各種類加載器最終會呈現(xiàn)樹狀結(jié)構(gòu)),如果父類加載器能夠完成,則返回父類加載器加載的Class實(shí)例;否則轉(zhuǎn)入步驟3;

3、調(diào)用本類加載器的 findClass() 方法,試圖獲取對應(yīng)的字節(jié)碼。如果獲取的到,則調(diào)用 defineClass() 導(dǎo)入類型到方法區(qū);如果獲取不到對應(yīng)的字節(jié)碼或者其他原因失敗,向上拋出異常。

在Java中,一個(gè)類用其完全匹配類名(fully qualified class name)作為標(biāo)識,這里指的完全匹配類名包括包名和類名。但在JVM中,一個(gè)類用其全名和一個(gè)ClassLoader的實(shí)例 作為唯一標(biāo)識,不同類加載器加載的類將被置于不同的命名空間。

在java.net包下有一個(gè) URLClassLoader 類提供了運(yùn)程加載類的功能,它讓我們可以通過以下幾種方式進(jìn)行加載:

* 文件: (從文件系統(tǒng)目錄加載)
* jar包: (從Jar包進(jìn)行加載)
* Http: (從遠(yuǎn)程的Http服務(wù)進(jìn)行加載)

一個(gè)class被一個(gè)ClassLoader實(shí)例加載過的話,就不能再被這個(gè)ClassLoader實(shí)例再次加載,即加載類時(shí)調(diào)用了defileClass()方法,重新加載字節(jié)碼、解析、驗(yàn)證后就不能重復(fù)執(zhí)行。系統(tǒng)默認(rèn)的AppClassLoader加載器內(nèi)部會緩存加載過的class,重新加載的話,就直接取緩存。所以需要熱加載只能重新創(chuàng)建一個(gè)ClassLoader,然后再去加載已經(jīng)被加載過的class文件。實(shí)現(xiàn)熱加載需要重載 ClassLoader 的 loadClass() 方法,跳過內(nèi)部的雙親委托機(jī)制。即使不采用雙親委托機(jī)制,比如java.lang包中的相關(guān)類還是不能自定義一個(gè)同名的類來代替,主要因?yàn)镴VM解析、驗(yàn)證class的 時(shí)候,會進(jìn)行相關(guān)判斷。強(qiáng)制重復(fù)加載引發(fā) java.lang.LinkageError 異常:

Exception in thread "main" java.lang.LinkageError: loader (instance of  NetworkClassLoader): attempted  duplicate class definition

JVM中class和Meta信息存放在PermGen space區(qū)域。如果加載的class文件很多,那么可能導(dǎo)致PermGen space區(qū)域空間溢出。引起:java.lang.OutOfMemoryErrorPermGen space. 對于有些Class我們可能只需要使用一次,就不再需要了,JVM中的Class只有滿足以下三個(gè)條件,才能被GC回收,也就是該Class被卸載(unload):

該類所有的實(shí)例都已經(jīng)被GC。
加載該類的ClassLoader實(shí)例已經(jīng)被GC。
該類的java.lang.Class對象沒有在任何地方被引用。
System.gc()執(zhí)行后的Class的卸載是不可控的。

自定義類加載器--遠(yuǎn)程類加載器

反射機(jī)制的應(yīng)用必須要求該類是public訪問權(quán)限的類,這樣才能由加載器加載。要實(shí)現(xiàn)HotSwap熱加載,只需要?jiǎng)?chuàng)建新實(shí)例去加載目標(biāo)類就可以,熱加載邏輯可以根據(jù)文件更新時(shí)間來做判斷。

import java.lang.reflect.*;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.net.URL;
import java.net.HttpURLConnection;

/**
 * NetworkClassLoader ncl = new NetworkClassLoader("http://xxx.com");  
 * String basicClassName = "Gum";  
 * Class<?> clazz = ncl.loadClass(basicClassName);
 * Gum oo = (Gum)clazz.newInstance();
 */
class NetworkClassLoader extends ClassLoader {

    private String rootUrl;

    public NetworkClassLoader(String rootUrl) {
        this.rootUrl = rootUrl;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getClassData(String className) {
        String path = rootUrl + "/" + className.replace('.', '/') + ".class";
        try {
            URL url = new URL(path);
        // HttpURLConnection con = (HttpURLConnection)url.openConnection();
        // System.out.println(con.getResponseCode?() +" "+ con.getContentType());
        // BufferedReader buffer = new BufferedReader(new InputStreamReader(ins));
        // StringBuffer bs = new StringBuffer();
        // String line  = null;
        // while((line=buffer.readLine())!=null){
        //  bs.append(line);
        // }
        // System.out.println("getClassData:"+bs.length()+":"+bs.substring(0));
        // return bs.toString().getBytes();
            InputStream ins = url.openStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int len = 0;
            while ((len = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            System.out.println("getClassData:"+len+":");
            return baos.toByteArray();
        } catch (Exception e) {
            System.out.println("getClassData exception:");
            e.printStackTrace();
        }
        System.out.println("getClassData null:");
        return null;
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class c = findLoadedClass(name);
        if( name.startsWith("java.") ){
            ClassLoader system = ClassLoader.getSystemClassLoader();
            c = system.loadClass(name);
        }
        if (null==c) c = findClass(name);
        if( resolve ) resolveClass(c);
        return c;
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容