概要:
- java類加載機制
Java類加載器除了根類加載器外,其他累加器都是使用Java語言編寫的,因此程序員完全可以開發自己的類加載器,通過使用自定義類
加載器
,可以完成一些特定的功能。 - java反射機制
重點介紹java.lang.reflect包下的接口和類,包括Class、Method、Field、Constructor和Array等,這些類分別代表類、方法、成員變量
、構造器和數組。java程序可以通過這些反射工具類動態地獲取某個對象、某個類的運行時信息,可以動態地創建Java對象,動態地調用Java
方法,訪問並修改指定對象的成員變量值。 - 使用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類。當程序主動使用任何一個類時,系統會保證該類
以及所有父類(包括直接父類和間接父類)都會被初始化
類初始化的時機
- 創建類的實例
包括用new操作符來創建實例,通過反射來創建實例,通過反序列化的方式來創建實例 - 調用某個類的類方法(靜態方法)
- 訪問某個類或接口的類變量,或爲該類變量賦值
- 使用反射方式來強制創建某個類或接口對應的java.lang.Class對象。例如代碼:Class.forName(“Person”)
如果系統還未初始化Person類,則這行代碼將會導致該Person類被初始化,並返回Person類對應的java.lang.class對象 - 初始化某個類的子類。當初始化某個類的子類時,該子類的所有父類都會被初始化。
- 使用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()來獲取系統類加載器。用戶自定義的類加載器如果沒有指定父加載器,則以系統類加載器爲父加載器。
類加載機制
- 全盤負責
當一個類加載器負責加載某個Class時,該Class所依賴的和引用
的其他Class也將有該類加載器負責載入,除非顯式使用另外一個
類加載器來載入。 - 雙親委託
所謂父類委託,則是先讓parent類加載器視圖加載該Class,只有在
父類加載器無法加載該類時才嘗試從自己的類路徑中加載類。
類加載器之間的父子關係並不是類繼承上的父子關係.這裏的父子關係
是類加載實例之間的關係。 - 緩存機制
緩存機制將會保證所有加載過的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()方法的執行步驟如下:
- 用findLoadedClass(String)來檢查是否已經加載類,如果已經加載則直接返回
- 在父類加載器上調用loadClass()。如果父類加載器爲null,則使用根加載器
來加載 - 調用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運算符進行判斷,在利用強制類型轉換將其轉換成
運行時類型的變量
- 用反射來獲取對象和類的真實信息
獲取Class對象的三種方式
- 使用Class類的forName(String clazzName)靜態方法。
參數是類的全限定類名 - 類的class屬性
- 對象的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操作符