文章目錄
一、什麼是Java反射機制(Reflection)
- Reflection(反射)是被視爲動態語言的關鍵,反射機制允許程序在執行期藉助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法。
- 加載完類之後,在堆內存的方法區中就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之爲:反射。
Java反射機制提供的功能 - 在運行時判斷任意一個對象所屬的類
- 在運行時構造任意一個類的對象
- 在運行時判斷任意一個類所具有的成員變量和方法
- 在運行時獲取泛型信息
- 在運行時調用任意一個對象的成員變量和方法
- 在運行時處理註解
- 生成動態代理
二、 Class類與創建Class的實例
1. Class 類
在Class類中有public final native Class<?> getClass();
。可以通過對象反射求出類的名稱。
Java源文件
Class 類
一個Class 對象包含了特定某個結構(class/interface/enum/annotation/primitive type/void/[])的有關信息
- Class本身也是一個類
- Class 對象只能由系統建立對象
- 一個加載的類在JVM 中只會有一個Class實例
- 一個Class對象對應的是一個加載到JVM中的一個.class文件
- 每個類的實例都會記得自己是由哪個Class 實例所生成
- 通過Class可以完整地得到一個類中的所有被加載的結構
- Class類是Reflection的根源,針對任何你想動態加載、運行的類,唯有先獲得相應的Class對象
Class類的常用方法
2.獲取Class類的實例
共四種方式
@Test public void test() throws ClassNotFoundException {
//1. 調用運行時類的屬性:.class
Class c1 = String.class;
System.out.println("c1:" + c1);
//2. 通過運行時類的對象,調用getClass()
String s1 = "";
Class c2 = s1.getClass();
System.out.println("c2:" + c2);
//3. 調用Class的靜態方法:forName(String classPath)
Class c3 = Class.forName("java.lang.Integer");
System.out.println("c3:" + c3);
//4. 使用類加載器 ClassLoader
ClassLoader classLoader = reflectDemo.class.getClassLoader();
Class c4 = classLoader.loadClass("java.lang.Object");
System.out.println("c4:" + c4);
}
哪些類型可以有Class對象?
三、 類的加載與ClassLoader的理解
1. 類加載的三個步驟
加載:
將class文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法區的運行時數據結構,然後生成一個代表這個類的java.lang.Class對象,作爲方法區中類數據的訪問入口(即引用地址)。所有需要訪問和使用類數據只能通過這個Class對象。這個加載的過程需要類加載器參與。
鏈接:
將Java類的二進制代碼合併到JVM的運行狀態之中的過程。
驗證:確保加載的類信息符合JVM規範,例如:以cafe開頭,沒有安全方面的問題
準備:正式爲類變量(static)分配內存並設置類變量默認初始值的階段,這些內存都將在方法區中進行分配。
解析:虛擬機常量池內的符號引用(常量名)替換爲直接引用(地址)的過程。
初始化:
執行類構造器()方法的過程。類構造器()方法是由編譯期自動收集類中所有類變量的賦值動作和靜態代碼塊中的語句合併產生的。(類構造器是構造類信息的,不是構造該類對象的構造器)。
當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
虛擬機會保證一個類的()方法在多線程環境中被正確加鎖和同步。
類加載器的作用:
- 類加載的作用:將class文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法區的運行時數據結構,然後在堆中生成一個代表這個類的java.lang.Class對象,作爲方法區中類數據的訪問入口。
- 類緩存:標準的JavaSE類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它將維持加載(緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象
2. ClassLoader
類加載器作用是用來把類(class)裝載進內存的。JVM 規範定義瞭如下類型的類的加載器。
- 引導類加載器:用C++編寫的,是JVM自帶的類加載器,負責Java平臺核心庫,用來裝載核心類庫。該加載器無法直接獲取
- 擴展類加載器:負責jre/lib/ext目錄下的jar包或–D java.ext.dirs指定目錄下的jar包裝入工作庫
- 系統類加載器:負責java –classpath或–D java.class.path所指的目錄下的類與jar包裝入工作,是最常用的加載器
獲取加載器
@Test
public void getClassLoader() throws ClassNotFoundException {
// 1.獲取一個系統類加載器
ClassLoader classloader= ClassLoader.getSystemClassLoader();
System.out.println(classloader);
// 2. 獲取系統類加載器的父類加載器,即擴展類加載器
classloader= classloader.getParent();
System.out.println(classloader);
// 3. 獲取擴展類加載器的父類加載器,即引導類加載器
classloader= classloader.getParent();
System.out.println(classloader);
// 4. 測試當前類由哪個類加載器進行加載
classloader= Class.forName("com.aaa.reflect.reflectDemo").getClassLoader();
System.out.println(classloader);
// 5. 測試JDK提供的Object類由哪個類加載器加載
classloader= Class.forName("java.lang.Object").getClassLoader();
System.out.println(classloader);
//6. 關於類加載器的一個主要方法:getResourceAsStream(String str):獲取類路徑下的指定文件的輸入流
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
System.out.println(inputStream);
}
四、創建運行時類的對象
調用Class對象的newInstance()方法
- 類必須有一個無參數的構造器。
- 類的構造器的訪問權限需要足夠。
沒有無參的構造器也可以創建對象
在操作的時候明確的調用類中的構造器,並將參數傳遞進去之後,纔可以實例化操作。
- . 步驟如下:
- 通過Class類的getDeclaredConstructor(Class … parameterTypes)取得本類的指定形參類型的構造器
- 向構造器的形參中傳遞一個對象數組進去,裏面包含了構造器中所需的各個參數。
- 通過Constructor實例化對象。
創建步驟
- 根據全類名獲取對應的Class對象
- 調用指定參數結構的構造器,生成Constructor的實例
- 通過Constructor的實例創建對應類的對象,並初始化類屬性
代碼實現
@Test
public void test3() throws Exception {
//1. 根據全類名獲取對應的Class對象
Class clazz = Class.forName("java.lang.String");
//2. 調用指定參數結構的構造器,生成Constructor的實例
Constructor constructor = clazz.getConstructor(String.class);
//3. 通過Constructor的實例創建對應類的對象,並初始化類屬性
String str = (String)constructor.newInstance("string");
System.out.println(str);
}
五、獲取運行時類的完整結構
使用反射獲取的內容
- 實現的全部接口
public Class<?>[] getInterfaces()
,確定此對象所表示的類或接口實現的接口。 - 所繼承的父類
public Class<? Super T> getSuperclass()
,返回表示此Class 所表示的實體(類、接口、基本類型)的父類的Class。 - 全部的構造器
public Constructor<T>[] getConstructors()
返回此Class 對象所表示的類的所有public構造方法。
public Constructor<T>[] getDeclaredConstructors()
返回此Class 對象表示的類聲明的所有構造方法。
Constructor類中:
- 取得修飾符:public intgetModifiers();
- 取得方法名稱: public String getName();
- 取得參數的類型:public Class<?>[] getParameterTypes();
- 全部的方法
public Method[] getDeclaredMethods()
返回此Class對象所表示的類或接口的全部方法
public Method[] getMethods()
返回此Class對象所表示的類或接口的public的方法
Method類中:
- public Class<?> getReturnType()取得全部的返回值
- public Class<?>[] getParameterTypes()取得全部的參數
- public int getModifiers()取得修飾符
- public Class<?>[] getExceptionTypes()取得異常信息
- 全部的Field
public Field[] getFields()
返回此Class對象所表示的類或接口的public的Field。
public Field[] getDeclaredFields()
返回此Class對象所表示的類或接口的全部Field。
Field方法中:
- public intgetModifiers() 以整數形式返回此Field的修飾符
- public Class<?> getType() 得到Field的屬性類型
- public String getName() 返回Field的名稱。
- Annotation相關
getAnnotation(Class<T> annotationClass)
獲取Annocation
getDeclaredAnnotations()
- 泛型相關
Type getGenericSuperclass()
獲取父類泛型類型
ParameterizedType
泛型類型
getActualTypeArguments()
獲取實際的泛型類型參數數組 - 類所在的包
Package getPackage()
代碼實現
@Test public void test4() throws Exception {
Class clazz = Class.forName("java.lang.String");
System.out.println("全部接口:" + Arrays.toString(clazz.getInterfaces()));
System.out.println("所繼承的父類:" + clazz.getSuperclass());
System.out.println("全部的構造器:" + Arrays.toString(clazz.getConstructors()));
System.out.println("全部的方法:" + Arrays.toString(clazz.getMethods()));
System.out.println("全部的Field:"+ Arrays.toString(clazz.getFields()));
System.out.println("Annotation:"+ clazz.getDeclaredAnnotations());
System.out.println("父類泛型:" + clazz.getGenericSuperclass());
System.out.println("類所在的包:" + clazz.getPackage());
}
六、調用運行時類的指定結構
1. 調用指定方法
通過反射,調用類中的方法,通過Method類完成。步驟:
- 通過Class類的getMethod(String name,Class…parameterTypes)方法取得一個Method對象,並設置此方法操作時所需要的參數類型。
- 使用Object invoke(Object obj, Object[] args)進行調用,並向方法中傳遞要設置的obj對象的參數信息。
Object invoke(Object obj, Object … args)
- Object 對應原方法的返回值,若原方法無返回值,此時返回null
- 若原方法若爲靜態方法,此時形參Object obj可爲null
- 若原方法形參列表爲空,則Object[] args爲null
- 若原方法聲明爲private,則需要在調用此invoke()方法前,顯式調用方法對象的setAccessible(true)方法,將可訪問private的方法。
2. 調用指定屬性
Field類
在反射機制中,可以直接通過Field類操作類中的屬性,通過Field類提供的set()和get()方法就可以完成設置和取得屬性內容的操作。
- public Field getField(String name) 返回此Class對象表示的類或接口的指定的public的Field。
- public Field getDeclaredField(String name)返回此Class對象表示的類或接口的指定的Field。
在Field中:
- public Object get(Object obj) 取得指定對象obj上此Field的屬性內容
- public void set(Object obj,Objectvalue) 設置指定對象obj上此Field的屬性內容
代碼示例
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
private int num = 100;
private int tt(String str){
System.out.println(str);
return 99;
}
private static void ss(){
System.out.println("hello static");
}
/**
* 如何操作運行時類中的指定的屬性
*/
@Test
public void testField1() throws Exception {
Class clazz = ReflectionTest.class;
//創建運行時類的對象
ReflectionTest s = (ReflectionTest) clazz.newInstance();
System.out.println("-------------------屬性--------------------");
//1. getDeclaredField(String fieldName):獲取運行時類中指定變量名的屬性
Field value = clazz.getDeclaredField("num");
//2.保證當前屬性是可訪問的
value.setAccessible(true);
//3.獲取、設置指定對象的此屬性值
value.set(s,1);
System.out.println(value.get(s));
System.out.println("-------------------方法--------------------");
// 1. 獲取方法 getDeclaredMethod():參數1 :指明獲取的方法的名稱 參數2:指明獲取的方法的形參列表
Method tt = clazz.getDeclaredMethod("tt",String.class);
//2. 保證當前方法是可訪問的
tt.setAccessible(true);
/** 3. 調用方法的invoke():參數1:方法的調用者 參數2:給方法形參賦值的實參
invoke()的返回值即爲對應類中調用的方法的返回值。
*/
Object returnValue = tt.invoke(s,"hello tt! ");
System.out.println(returnValue);
System.out.println("-------------------靜態方法--------------------");
//如何調用靜態方法
Method ss = clazz.getDeclaredMethod("ss");
ss.setAccessible(true);
Object result = ss.invoke(s);
System.out.println(result);
System.out.println("-------------------構造方法--------------------");
Class clazz1 = Integer.class;
//創建運行時類的對象
//1. 獲取指定的構造器
Constructor constructor = clazz1.getDeclaredConstructor(String.class);
//2.保證此構造器是可訪問的
constructor.setAccessible(true);
//3. 調用此構造器創建運行時類的對象
Integer results = (Integer) constructor.newInstance("50");
System.out.println(results);
}
}
七、反射的應用:動態代理
1.代理設計模式的原理:
使用一個代理將對象包裝起來, 然後用該代理對象取代原始對象。任何對原始對象的調用都要通過代理。代理對象決定是否以及何時將方法調用轉到原始對象上。
2. Java動態代理相關API Proxy
專門完成代理的操作類,是所有動態代理類的父類。通過此類爲一個或多個接口動態地生成實現類。
提供用於創建動態代理類和動態代理對象的靜態方法
static Class<?> getProxyClass(ClassLoader loader, Class<?>...interfaces)
創建一個動態代理類所對應的Class對象static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
直接創建一個動態代理對象
3. 動態代理與AOP(Aspect Orient Programming)
- 使用Proxy生成一個動態代理時,往往並不會憑空產生一個動態代理,這樣沒有太大的意義。通常都是爲指定的目標對象生成動態代理
- 這種動態代理在AOP中被稱爲AOP代理,AOP代理可代替目標對象,AOP代理包含了目標對象的全部方法。但AOP代理中的方法與目標對象的方法存在差異:AOP代理裏的方法可以在執行目標方法之前、之後插入一些通用處理
4. 動態代理步驟
- 創建一個實現接口InvocationHandler的類,它必須實現invoke方法,以完成代理的具體操作。
- 創建被代理的類以及接口
- 通過Proxy的靜態方法創建一個Subject接口代理
- 通過Subject代理調用RealSubject實現類的方法
5. 代碼實現
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
Porsche porsche = new Porsche();
//proxyInstance:代理類的對象
Car proxyInstance = (Car)ProxyFactory.getProxyInstance(porsche);
//當通過代理類對象調用方法時,會自動的調用被代理類中同名的方法
String belief = proxyInstance.getName();
System.out.println(belief);
proxyInstance.buy(2000000);
}
}
interface Car {
String getName();
void buy(int money);
}
//被代理類
class Porsche implements Car {
@Override public String getName() {
return "保時捷";
}
@Override public void buy(int money) {
System.out.println("買車花費:" + money);
}
}
class CarUtil {
public void method1() {
System.out.println("--------------------通用方法1--------------------");
}
public void method2() {
System.out.println("--------------------通用方法2--------------------");
}
}
class ProxyFactory {
//調用此方法,返回一個代理類的對象。解決問題一
public static Object getProxyInstance(Object obj) {//obj:被代理類的對象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}
class MyInvocationHandler implements InvocationHandler {
private Object obj;//需要使用被代理類的對象進行賦值
public void bind(Object obj) {
this.obj = obj;
}
//當我們通過代理類的對象,調用方法a時,就會自動的調用如下的方法:invoke()
//將被代理類要執行的方法a的功能就聲明在invoke()中
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
CarUtil util = new CarUtil();
util.method1();
//method:即爲代理類對象調用的方法,此方法也就作爲了被代理類對象要調用的方法
//obj:被代理類的對象
Object returnValue = method.invoke(obj, args);
util.method2();
//上述方法的返回值就作爲當前類中的invoke()的返回值。
return returnValue;
}
}