java類加載器與反射學習筆記

概要:

  1. java類加載機制
    Java類加載器除了根類加載器外,其他累加器都是使用Java語言編寫的,因此程序員完全可以開發自己的類加載器,通過使用自定義類
    加載器
    ,可以完成一些特定的功能。
  2. java反射機制
    重點介紹java.lang.reflect包下的接口和類,包括Class、Method、Field、Constructor和Array等,這些類分別代表類、方法、成員變量
    、構造器和數組。java程序可以通過這些反射工具類動態地獲取某個對象、某個類的運行時信息,可以動態地創建Java對象,動態地調用Java
    方法,訪問並修改指定對象的成員變量值。
  3. 使用Proxy和InvocationHandler實現JDK動態代理
    通過JDK動態代理向讀者介紹高層次解耦的方法,還會講解JDK動態代理和AOP(Aspect Orient Programming,面向切面編程)
    之間的內在關係

類的加載、連接和初始化

系統可能在第一次使用某個類時加載該類,也可能採用預加載機制來加載某個類。

jvm和類

同一個JVM的所有線程、所有變量都處於同一個進程裏,他們都使用該JVM進程的內存區。當系統出現以下幾種情況時,JVM進程將被終止
1. 程序運行到最後正常結束
2. 程序運行到使用System.exit()或Runtime.getRuntime().exit()代碼處結束程序。
3. 程序執行過程中遇到爲捕獲的異常或錯誤而結束。
4. 程序所在平臺強制結束了JVM進程

類的加載

當程序主動使用某個類時,如果該類還未被加載到內存中,這系統會通過加載、連接、初始化三個步驟來對該類進行初始化。
類的二進制數據的來源:
1. 從本地文件系統加載class文件
2. 從JAR包加載class文件,JDBC編程時用到的數據庫驅動類就放在JAR文件中(mysql-connector.jar)
JVM可以從JAR文件中直接加載該class文件
3. 通過網絡加載class文件
4. 把一個java源文件動態編譯,並執行加載

類加載器無須等到”首次使用”該類時才加載該類,java虛擬機規範允許系統預先加載某些類。

類的連接

當類加載後,系統爲之生成一個對應的Class對象,接着將會進入連接階段,連接階段負責把類的二進制數據合併到JRE中。
類連接可分爲如下三個階段:
(1) 驗證: 驗證階段用於檢驗被加載的類是否有正確的內部結構,並和其他類協調一致
(2) 準備: 類準備階段則負責爲類的類變量分配內存,並設置默認初始值
(3) 解析: 將類的二進制數據中的符號引用替換成直接引用

類的初始化

在類的初始化階段,虛擬機負責對類進行初始化,主要就是對類變量進行初始化。在Java類中對類變量指定初始值有兩種方式:
1. 聲明類變量時指定初始值
2. 使用靜態代碼塊爲類變量指定初始值。
JVM初始化一個類包含如下幾個步驟:
1. 假如這個類還沒有被加載和連接,則程序先加載並連接該類
2. 假如該類的直接父類還沒有被初始化,則先初始化其直接父類
3. 假如類中有初始化語句,則系統依次執行這些初始化語句
當執行第2個步驟時,系統對直接父類的初始化步驟也遵循此步驟1~3;如果該父類又有直接父類,則系統再次再次重複這三個步驟來
先初始化這個父類….. 依此類推,JVM最先初始化的總是java.lang.Object類。當程序主動使用任何一個類時,系統會保證該類
以及所有父類(包括直接父類和間接父類)都會被初始化

類初始化的時機

  1. 創建類的實例
    包括用new操作符來創建實例,通過反射來創建實例,通過反序列化的方式來創建實例
  2. 調用某個類的類方法(靜態方法)
  3. 訪問某個類或接口的類變量,或爲該類變量賦值
  4. 使用反射方式來強制創建某個類或接口對應的java.lang.Class對象。例如代碼:Class.forName(“Person”)
    如果系統還未初始化Person類,則這行代碼將會導致該Person類被初始化,並返回Person類對應的java.lang.class對象
  5. 初始化某個類的子類。當初始化某個類的子類時,該子類的所有父類都會被初始化。
  6. 使用java命令運行某個主類。當運行某個主類時,程序會先初始化該主類。

類加載器

類加載器簡介

類加載器負責將.class 文件(可能在磁盤上,也可能在網絡上)加載到內存中,併爲之生成對應的java.lang.Class對象。
一旦一個類被載入JVM中,同一個類就不會被再次載入了。
正如一個對象有一個唯一的標誌一樣,一個載入JVM的類也有一個唯一的標識。在java中,一個類用其全限定類名(包括包名和類名)
作爲標誌;但在JVM中,一個類用全限定類名和其類加載器作爲唯一標識。這意味着兩個類加載器加載的同名類(Person、pg、k1)和
(Person、pg、k2)是不同的、他們加載的類也是完全不同、互不兼容
當JVM啓動時,會形成有3個類加載器組成的初始類加載器層次結構

Bootstrap ClassLoader:

根類加載器,負責加載java的核心類在執行java.exe命令時,使用-Xbootclasspath選項或使用-D選項指定sun.boot.class.path系統屬性值可以指定加載附加的類。根加載器不是java.lang.ClassLoader的子類,而是由JVM自身實現的。

Extension ClassLoader:

擴展類加載器,負責加載JRE的擴展目錄(%JAVA_HOME%/jre/lib/ext 或者 由java.ext.dirs系統屬性指定的目錄)中jar包的類(自己開發中要用到的類可以放到該目錄中,讓jvm自動加載,爲java擴展核心類以外的新功能)

System ClassLoader:

系統類加載器,也稱爲應用加載器,負責在jvm啓動時加載來自java命令的-classpath選項、java.class.path系統屬性或CLASSPATH環境變量所指定的JAR包和類路徑。程序可以通過ClassLoader的靜態方法getSystemClassloader()來獲取系統類加載器。用戶自定義的類加載器如果沒有指定父加載器,則以系統類加載器爲父加載器。

類加載機制

  1. 全盤負責
    當一個類加載器負責加載某個Class時,該Class所依賴的和引用
    的其他Class也將有該類加載器負責載入,除非顯式使用另外一個
    類加載器來載入。
  2. 雙親委託
    所謂父類委託,則是先讓parent類加載器視圖加載該Class,只有在
    父類加載器無法加載該類時才嘗試從自己的類路徑中加載類。
    類加載器之間的父子關係並不是類繼承上的父子關係.這裏的父子關係
    是類加載實例之間的關係。
  3. 緩存機制
    緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用
    某個Class時,類加載器先從緩存區中搜尋該Class,只有當緩存區不存在
    該Class對象時,系統纔會讀取該類對應的二進制數據,並將其轉換成
    Class對象,存入緩存區中。當修改了一個類後,必須重新啓動jvm,讓jvm
    重新將二進制數據加載到內存中轉換爲Class對象後修改才能生效。
    不重啓jvm,jvm總是使用緩存中的舊的Class對象。

系統類加載器是AppClassLoader的實例
擴展類加載器是ExtClassLoaderr的實例
AppClassLoader和ExtClassLoader都是URLClassLoader的實例

類加載器加載Class大致要經過如下8個步驟:
1. 檢測此Class是否載入過(即在緩存區中是否有此Class),若有直接
進入第8步,否則執行第2步.
2. 如果父類加載器不存在(如果沒有父類加載器,要麼parent是根類加載器,
要麼本身就是根類加載器),跳到第4步執行;如果父類存在,則跳到第3步。
3. 請求使用父類加載器去載入目標類,如果載入成功則跳到第8步,否則跳到
第7步。
4. 請求使用根類加載器來載入目標類,如果成功載入則跳到第8步,否則接着執行第
5步。
5. 當前類加載器嘗試尋找Class文件(從此ClassLoader相關的類路徑中尋找),如果
找到則執行第6步,如果找不到則跳到第7步。
6. 從文件中載入Class,成功載入後跳到第8步。
7. 拋出ClassNotFoundException異常。
8. 返回對應的java.lang.Class對象

第5、6步允許重寫ClassLoader的findClass()方法來實現自己的載入策略,甚至重寫
loadClass()方法來實現自己的載入過程

創建並使用自定義的類加載器

JVM中除根類之外的所有類加載器都是ClassLoader子類的實例,開發者可以通過
擴展ClassLoader的子類,並重寫該ClassLoader所包含的方法倆實現自定義的類加載
器。
ClassLoader中包含了大量的protected方法,即這些方法可以被子類重寫。
ClassLoader類有如下兩個關鍵方法:

  • loadClass(String name,boolean resolve)
    該方法爲ClassLoader的入口點,loadClass()方法的執行步驟如下:
    1. 用findLoadedClass(String)來檢查是否已經加載類,如果已經加載則直接返回
    2. 在父類加載器上調用loadClass()。如果父類加載器爲null,則使用根加載器
      來加載
    3. 調用findClass(String)方法查找類
  • findClass(String)

如果需要實現自定義ClassLoader,一般重寫findClass()方法,避免覆蓋默認類加載器
的父類委託、緩衝機制兩種策略

ClassLoader的另一個核心方法:
defineClass(String name,byte[] b,int off,int len)
該方法負責將指定類的字節碼文件(即Class文件)讀入字節數組byte[] b內,並轉換
爲Class對象,該字節碼文件可以來源於文件系統、網絡等

ClassLoader 的其他普通方法:

  • findSystemClass(String name):從本地文件系統裝入文件。它在本地文件系統中尋找
    類文件,如果存在,就使用defineClass(String name)方法將原始字節轉換成Class對象
    將該文件轉換成類
  • static getSystemClassLoader():這是一個靜態方法,用於返回系統類加載器
  • getParent():獲取該類加載器的父類加載器
  • resovleClass(Class

URLClassloader

URlClassLoader classLoader= new URLClassLoader(urls);
創建URLClassLoader時傳入一個URL數組參數,該ClassLoader就可以從
這系列URL指定的資源中加載指定類。
url以file:爲前綴,表明從本地文件系統加載
url以http:爲前綴,表明從互聯網通過http協議來加載
url以ftp:爲前綴,表明從互聯網通過ftp訪問來加載

通過反射查看類信息

在運行時發現對象和類的真實信息的兩種做法:
1. 先用instanceof運算符進行判斷,在利用強制類型轉換將其轉換成
運行時類型的變量

  1. 用反射來獲取對象和類的真實信息

獲取Class對象的三種方式

  1. 使用Class類的forName(String clazzName)靜態方法。
    參數是類的全限定類名
  2. 類的class屬性
  3. 對象的getClass()方法。該方法是java.lang.Object類中的一個方法

第2種方式的好處:
* 在編譯階段而不是在運行階段賦值,有編譯器檢查,代碼更安全
* 性能更好,不需要在運行時調用方法,在編譯器就得出值

從Class中獲取信息

取Class對應類所包含的構造器

 Constructor<T> getConstructor(Class<?> ...parameterTypes)

返回此Class對象對應類的、帶指定形參列表(parameterTypes)的public構造器

Constructor<?>[] getConstructors()

返回此Class對象對應類的所有public構造器

 Constructor<T> getDeclaredConstructor(Class<?> ...parameterTypes)

返回此Class對象對應類、帶指定形參列表的構造器,與構造器的訪問權限無關

Constructor<?>[] getDeclaredConstructors()

返回此Class對象對應類的所有構造器,與構造器的訪問權限無關

獲取Class對應類所包含的方法

Method getMethod(String name,Class<?> ...parameterTypes)

返回此Class對象對應類的、帶指定形參的public

Method getMethods()

返回此Class對象所表示的類的所有public方法

Method getDeclaredMethod(String name,Class<?> ...parameterTypes)

返回此Class對象所對應類的、帶指定形參列表的方法,與方法的訪問權限無關

Method getDeclaredMethods()

返回Class對象對應類的全部方法,與方法的訪問權限無關

獲取Class對應類包含的成員變量

Field getField(String name)

返回此Class對象對應類的、指定名稱的public成員變量

Field[] getFields()

返回此Class對象所有的public成員變量

Field getDeclaredField(String name)

返回Class對象對應類的、指定名稱的成員變量,與成員變量的訪問權限無關

Field[] getDeclaredFields()

返回此Class對象對應類的全部成員變量,與成員變量的訪問權限無關。

訪問Class對應類上所包含的Annotation

<A extends Annotation> A getAnnotation(Class<A> annotationClass)

嘗試獲取該Class對象對應類上存在的、指定類型的Annotation: 如果該類型的
註解不存在,則返回null

<A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)

嘗試獲取直接修飾該Class對象對應的類的存在的指定類型的
(直接修飾應該指的是不是繼承來的註解和通過實現接口得到的註解)

Annotation[] getAnnotations()

返回修飾該Class對象對應類上存在的所有Annotation

Annotation[] getDeclaredAnnotations()

返回直接修飾該Class對象對應類上存在的所有Annotation

<A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass)

由於java8增加了重複註解功能,因此需要使用該方法獲取直接修飾該類的、指定
類型的多個Annotation

<A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass)

由於java8增加了重複註解功能,因此需要使用該方法獲取直接修飾該類的、指定
類型的多個Annotation

訪問內部類

Class<?> getDeclaredClasses()

返回該Class對象對應類裏包含的全部內部類

訪問外部類

Class<?> getDeclaringClass()

返回該Class對象對應類所在的外部類

Class<?>[] getInterfaces()

返回該Class對象對應類所實現的全部接口

訪問該Class對象對應類所繼承的父類

Class<? super T > getSuperclass()

獲得Class對象對應類的修飾符、所在包、類名

int getModifiers()

獲取類或接口的修飾符

Package getPackage()

獲取類的包

String getName()

以字符串形式返回此Class對象所表示的類的簡稱

String getSimpleName()

返回此Class對象所表示的類的簡稱

判斷該類是否爲接口、枚舉、註解類型

boolean isAnnotation()

返回此Class對象是否表示一個註解類型(由@interface定義)

boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

判斷此Class對象是否使用了Annotation修飾

boolean isAnonymousClass()

返回此Class對象是否是一個匿名類

boolean isArray()

返回此Class對象是否表示一個數組

boolean isEnum()

返回此Class對象是否表示一個枚舉

boolean isInterface()

返回此Class對象是否表示一個接口

boolean isInstance(Object obj)

判斷obj是否是此Class對象的實例,該方法可以完全代替instanceof操作符

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章