1. 程式人生 > 實用技巧 >JavaWeb 之 Http

JavaWeb 之 Http

第16章 反射(Reflect)

第16章 反射(Reflect)

16.1 類載入

類在記憶體中的生命週期:載入–>使用–>解除安裝

16.1.1 類的載入過程

當程式主動使用某個類時,如果該類還未被載入到記憶體中,系統會通過載入、連線、初始化三個步驟來對該類進行初始化,如果沒有意外,JVM將會連續完成這三個步驟,所以有時也把這三個步驟統稱為類載入。

類的載入又分為三個階段:

(1)載入:load

就是指將型別的clas s位元組碼資料讀入記憶體

(2)連線:link

①驗證:校驗合法性等

②準備:準備對應的記憶體(方法區),建立Class物件,為類變數賦預設值,為靜態常量賦初始值。

③解析:把位元組碼中的符號引用替換為對應的直接地址引用

(3)初始化:initialize(類初始化)即執行類初始化方法,大多數情況下,類的載入就完成了類的初始化,有些情況下,會延遲類的初始化。
在這裡插入圖片描述

16.1.2 類初始化

1、哪些操作會導致類的初始化?

(1)執行主方法所在的類,要先完成類初始化,再執行main方法

(2)第一次使用某個型別就是在new它的物件,此時這個類沒有初始化的話,先完成類初始化再做例項初始化

(3)呼叫某個類的靜態成員(類變數和類方法),此時這個類沒有初始化的話,先完成類初始化

(4)子類初始化時,發現它的父類還沒有初始化的話,那麼先初始化父類

(5)通過反射操作某個類時,如果這個類沒有初始化,也會導致該類先初始化

類初始化執行的是(),該方法由(1)類變數的顯式賦值程式碼(2)靜態程式碼塊中的程式碼構成
2、哪些使用類的操作,但是不會導致類的初始化?

(1)使用某個類的靜態的常量(static final)

(2)通過子類呼叫父類的靜態變數,靜態方法,只會導致父類初始化,不會導致子類初始化,即只有宣告靜態成員的類才會初始化

(3)用某個型別宣告陣列並建立陣列物件時,不會導致這個類初始化

16.1.3 類載入器

很多開發人員都遇到過java.lang.ClassNotFoundException或java.lang.NoClassDefError,想要更好的解決這類問題,或者在一些特殊的應用場景,比如需要支援類的動態載入或需要對編譯後的位元組碼檔案進行加密解密操作,那麼需要你自定義類載入器,因此瞭解類載入器及其類載入機制也就成了每一個Java開發人員的必備技能之一。

1、類載入器分為:

(1)引導類載入器(Bootstrap Classloader)又稱為根類載入器

它負責載入jre/rt.jar核心庫
它本身不是Java程式碼實現的,也不是ClassLoader的子類,獲取它的物件時往往返回null

(2)擴充套件類載入器(Extension ClassLoader)

它負責載入jre/lib/ext擴充套件庫
它是ClassLoader的子類

(3)應用程式類載入器(Application Classloader)

它負責載入專案的classpath路徑下的類

它是ClassLoader的子類

(4)自定義類載入器

當你的程式需要載入“特定”目錄下的類,可以自定義類載入器;
當你的程式的位元組碼檔案需要加密時,那麼往往會提供一個自定義類載入器對其進行解碼
後面會見到的自定義類載入器:tomcat中

2、Java系統類載入器的雙親委託模式

簡單描述:

下一級的類載入器,如果接到任務時,會先搜尋是否載入過,如果沒有,會先把任務往上傳,如果都沒有載入過,一直到根載入器,如果根載入器在它負責的路徑下沒有找到,會往回傳,如果一路回傳到最後一級都沒有找到,那麼會報ClassNotFoundException或NoClassDefError,如果在某一級找到了,就直接返回Class物件。

應用程式類載入器 把 擴充套件類載入器視為父載入器,

擴充套件類載入器 把 引導類載入器視為父載入器。

不是繼承關係,是組合的方式實現的。

16.2 javalang.Class類

Java反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為Java語言的反射機制。

要想解剖一個類,必須先要獲取到該類的Class物件。而剖析一個類或用反射解決具體的問題就是使用相關API(1)java.lang.Class(2)java.lang.reflect.*。所以,Class物件是反射的根源。

1、哪些型別可以獲取Class物件

所有Java型別

用程式碼示例

//(1)基本資料型別和void
例如:int.class
	 void.class
//(2)類和介面
例如:String.class
	Comparable.class
//(3)列舉
例如:ElementType.class
//(4)註解
例如:Override.class
//(5)陣列
例如:int[].class

2、獲取Class物件的四種方式

(1)型別名.class

要求編譯期間已知型別

(2)物件.getClass()

獲取物件的執行時型別

(3)Class.forName(型別全名稱)

可以獲取編譯期間未知的型別

(4)ClassLoader的類載入器物件.loadClass(型別全名稱)

可以用系統類載入物件或自定義載入器物件載入指定路徑下的型別

public class TestClass {
	@Test
	public void test05() throws ClassNotFoundException{
		Class c = TestClass.class;
		ClassLoader loader = c.getClassLoader();
		
		Class c2 = loader.loadClass("com.atguigu.test05.Employee");
		Class c3 = Employee.class;
		System.out.println(c2 == c3);
	}
	
	@Test
	public void test03() throws ClassNotFoundException{
		Class c2 = String.class;
		Class c1 = "".getClass();
		Class c3 = Class.forName("java.lang.String");
		
		System.out.println(c1 == c2);
		System.out.println(c1 == c3);
	}
}

3、檢視某個類的類載入器物件

//獲取應用程式類載入器物件


//獲取擴充套件類載入器物件


//獲取根載入器物件

16.3 反射的應用

16.3.1 獲取型別的詳細資訊

可以獲取:包、修飾符、型別名、父類(包括泛型父類)、父介面(包括泛型父介面)、成員(屬性、構造器、方法)、註解(類上的、方法上的、屬性上的)

示例程式碼獲取常規資訊:

public class TestClassInfo {
	public static void main(String[] args) throws NoSuchFieldException, SecurityException {
		//1、先得到某個型別的Class物件
		Class clazz = String.class;
		//比喻clazz好比是鏡子中的影子
		
		//2、獲取類資訊
		//(1)獲取包物件,即所有java的包,都是Package的物件
		Package pkg = clazz.getPackage();
		System.out.println("包名:" + pkg.getName());
		
		//(2)獲取修飾符
		//其實修飾符是Modifier,裡面有很多常量值
		/*
		 * 0x是十六進位制
		 * PUBLIC           = 0x00000001;  1    1
		 * PRIVATE          = 0x00000002;  2	10
		 * PROTECTED        = 0x00000004;  4	100
		 * STATIC           = 0x00000008;  8	1000
		 * FINAL            = 0x00000010;  16	10000
		 * ...
		 * 
		 * 設計的理念,就是用二進位制的某一位是1,來代表一種修飾符,整個二進位制中只有一位是1,其餘都是0
		 * 
		 * mod = 17          0x00000011
		 * if ((mod & PUBLIC) != 0)  說明修飾符中有public
		 * if ((mod & FINAL) != 0)   說明修飾符中有final
		 */
		int mod = clazz.getModifiers();
		System.out.println(Modifier.toString(mod));
		
		//(3)型別名
		String name = clazz.getName();
		System.out.println(name);
		
		//(4)父類,父類也有父類對應的Class物件
		Class superclass = clazz.getSuperclass();
		System.out.println(superclass);
		
		//(5)父介面們
		Class[] interfaces = clazz.getInterfaces();
		for (Class class1 : interfaces) {
			System.out.println(class1);
		}
		
		//(6)類的屬性,  你宣告的一個屬性,它是Field的物件
/*		Field clazz.getField(name)  根據屬性名獲取一個屬性物件,但是隻能得到公共的
		Field[] clazz.getFields();  獲取所有公共的屬性
		Field clazz.getDeclaredField(name)  根據屬性名獲取一個屬性物件,可以獲取已宣告的
		Field[] clazz.getDeclaredFields()	獲取所有已宣告的屬性
		*/
		Field valueField = clazz.getDeclaredField("value");
//		System.out.println("valueField = " +valueField);
		
		Field[] declaredFields = clazz.getDeclaredFields();
		for (Field field : declaredFields) {
			//修飾符、資料型別、屬性名    
			int modifiers = field.getModifiers();
			System.out.println("屬性的修飾符:" + Modifier.toString(modifiers));
			
			String name2 = field.getName();
			System.out.println("屬性名:" + name2);
			
			Class<?> type = field.getType();
			System.out.println("屬性的資料型別:" + type);
		}
		System.out.println("-------------------------");
		//(7)構造器們
		Constructor[] constructors = clazz.getDeclaredConstructors();
		for (Constructor constructor : constructors) {
			//修飾符、構造器名稱、構造器形參列表  、丟擲異常列表
			int modifiers = constructor.getModifiers();
			System.out.println("構造器的修飾符:" + Modifier.toString(modifiers));
			
			String name2 = constructor.getName();
			System.out.println("構造器名:" + name2);
			
			//形參列表
			System.out.println("形參列表:");
			Class[] parameterTypes = constructor.getParameterTypes();
			for (Class parameterType : parameterTypes) {
				System.out.println(parameterType);
			}
            
            //異常列表
			System.out.println("異常列表:");
			Class<?>[] exceptionTypes = constructor.getExceptionTypes();
			for (Class<?> exceptionType : exceptionTypes) {
				System.out.println(exceptionType);
			}
		}
		System.out.println("=--------------------------------");
		//(8)方法們
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method method : declaredMethods) {
			//修飾符、返回值型別、方法名、形參列表 、異常列表 
			int modifiers = method.getModifiers();
			System.out.println("方法的修飾符:" + Modifier.toString(modifiers));
			
			Class<?> returnType = method.getReturnType();
			System.out.println("返回值型別:" + returnType);
			
			String name2 = method.getName();
			System.out.println("方法名:" + name2);
			
			//形參列表
			System.out.println("形參列表:");
			Class[] parameterTypes = method.getParameterTypes();
			for (Class parameterType : parameterTypes) {
				System.out.println(parameterType);
			}
			
			//異常列表
			System.out.println("異常列表:");
			Class<?>[] exceptionTypes = method.getExceptionTypes();
			for (Class<?> exceptionType : exceptionTypes) {
				System.out.println(exceptionType);
			}
		}
		
	}
}

16.3.2 建立任意引用型別的物件

兩種方式:

1、直接通過Class物件來例項化(要求必須有無參構造)

2、通過獲取構造器物件來進行例項化

方式一的步驟:

(1)獲取該型別的Class物件(2)建立物件

	@Test
	public void test2()throws Exception{
		Class<?> clazz = Class.forName("com.atguigu.test.Student");
		//Caused by: java.lang.NoSuchMethodException: com.atguigu.test.Student.<init>()
		//即說明Student沒有無參構造,就沒有無參例項初始化方法<init>
		Object stu = clazz.newInstance();
		System.out.println(stu);
	}
	
	@Test
	public void test1() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//		AtGuigu obj = new AtGuigu();//編譯期間無法建立
		
		Class<?> clazz = Class.forName("com.atguigu.test.AtGuigu");
		//clazz代表com.atguigu.test.AtGuigu型別
		//clazz.newInstance()建立的就是AtGuigu的物件
		Object obj = clazz.newInstance();
		System.out.println(obj);
	}

方式二的步驟:

(1)獲取該型別的Class物件(2)獲取構造器物件(3)建立物件

如果構造器的許可權修飾符修飾的範圍不可見,也可以呼叫setAccessible(true)

示例程式碼:

public class TestNewInstance {
	@Test
	public void test3()throws Exception{
		//(1)獲取Class物件
		Class<?> clazz = Class.forName("com.atguigu.test.Student");
		/*
		 * 獲取Student型別中的有參構造
		 * 如果構造器有多個,我們通常是根據形參【型別】列表來獲取指定的一個構造器的
		 * 例如:public Student(int id, String name) 
		 */
		//(2)獲取構造器物件
		Constructor<?> constructor = clazz.getDeclaredConstructor(int.class,String.class);
		
		//(3)建立例項物件
		// T newInstance(Object... initargs)  這個Object...是在建立物件時,給有參構造的實參列表
		Object obj = constructor.newInstance(2,"張三");
		System.out.println(obj);
	}
	
}

16.3.3 操作任意型別的屬性

(1)獲取該型別的Class物件
Class clazz = Class.forName(“com.atguigu.bean.User”);

(2)獲取屬性物件
Field field = clazz.getDeclaredField(“username”);

(3)設定屬性可訪問

field.setAccessible(true);

(4)建立例項物件:如果操作的是非靜態屬性,需要建立例項物件
Object obj = clazz.newInstance();

(4)設定屬性值

field.set(obj,“chai”);
(5)獲取屬性值
Object value = field.get(obj);

如果操作靜態變數,那麼例項物件可以省略,用null表示

示例程式碼:

public class TestField {
	public static void main(String[] args)throws Exception {
		//1、獲取Student的Class物件
		Class clazz = Class.forName("com.atguigu.test.Student");
		
		//2、獲取屬性物件,例如:id屬性
		Field idField = clazz.getDeclaredField("id");
        
        //3、如果id是私有的等在當前類中不可訪問access的,我們需要做如下操作
		idField.setAccessible(true);
		
		//4、建立例項物件,即,建立Student物件
		Object stu = clazz.newInstance();
				
		//5、獲取屬性值
		/*
		 * 以前:int 變數= 學生物件.getId()
		 * 現在:Object id屬性物件.get(學生物件)
		 */
		Object value = idField.get(stu);
		System.out.println("id = "+ value);
		
		//6、設定屬性值
		/*
		 * 以前:學生物件.setId(值)
		 * 現在:id屬性物件.set(學生物件,值)
		 */
		idField.set(stu, 2);
		
		value = idField.get(stu);
		System.out.println("id = "+ value);
	}
}

16.3.4 呼叫任意型別的方法

(1)獲取該型別的Class物件
Class clazz = Class.forName(“com.atguigu.service.UserService”);
(2)獲取方法物件
Method method = clazz.getDeclaredMethod(“login”,String.class,String.class);
(3)建立例項物件
Object obj = clazz.newInstance();
(4)呼叫方法
Object result = method.invoke(obj,“chai”,"123);

如果方法的許可權修飾符修飾的範圍不可見,也可以呼叫setAccessible(true)

如果方法是靜態方法,例項物件也可以省略,用null代替

示例程式碼:

public class TestMethod {
	@Test
	public void test()throws Exception {
		// 1、獲取Student的Class物件
		Class<?> clazz = Class.forName("com.atguigu.test.Student");
		
		//2、獲取方法物件
		/*
		 * 在一個類中,唯一定位到一個方法,需要:(1)方法名(2)形參列表,因為方法可能過載
		 * 
		 * 例如:void setName(String name)
		 */
		Method method = clazz.getDeclaredMethod("setName", String.class);
		
		//3、建立例項物件
		Object stu = clazz.newInstance();
		
		//4、呼叫方法
		/*
		 * 以前:學生物件.setName(值)
		 * 現在:方法物件.invoke(學生物件,值)
		 */
		method.invoke(stu, "張三");
		
		System.out.println(stu);
	}
}

16.3.5 獲取泛型父類資訊

示例程式碼獲取泛型父類資訊:

/* Type:
 * (1)Class
 * (2)ParameterizedType   
 * 		例如:Father<String,Integer>
 * 			ArrayList<String>
 * (3)TypeVariable
 * 		例如:T,U,E,K,V
 * (4)WildcardType
 * 		例如:
 * 		ArrayList<?>
 * 		ArrayList<? super 下限>
 * 		ArrayList<? extends 上限>
 * (5)GenericArrayType
 * 		例如:T[]
 * 	
 */
public class TestGeneric {
	public static void main(String[] args) {
		//需求:在執行時,獲取Son型別的泛型父類的泛型實參<String,Integer>
		
		//(1)還是先獲取Class物件
		Class clazz = Son.class;//四種形式任意一種都可以
		
		//(2)獲取泛型父類
//		Class sc = clazz.getSuperclass();
//		System.out.println(sc);
		/*
		 * getSuperclass()只能得到父類名,無法得到父類的泛型實參列表
		 */
		Type type = clazz.getGenericSuperclass();
		
		// Father<String,Integer>屬於ParameterizedType
		ParameterizedType pt = (ParameterizedType) type;
		
		//(3)獲取泛型父類的泛型實參列表
		Type[] typeArray = pt.getActualTypeArguments();
		for (Type type2 : typeArray) {
			System.out.println(type2);
		}
	}
}
//泛型形參:<T,U>
class Father<T,U>{
	
}
//泛型實參:<String,Integer>
class Son extends Father<String,Integer>{
	
}

16.3.6 讀取註解資訊

示例程式碼讀取註解資訊:

public class TestAnnotation {
	public static void main(String[] args) {
		//需求:可以獲取MyClass型別上面配置的註解@MyAnnotation的value值
		
		//讀取註解
//		(1)獲取Class物件
		Class<MyClass> clazz = MyClass.class;
		
		//(2)獲取註解物件
		//獲取指定註解物件
		MyAnnotation my = clazz.getAnnotation(MyAnnotation.class);
		
		//(3)獲取配置引數值
		String value = my.value();
		System.out.println(value);
	}
}
//宣告
@Retention(RetentionPolicy.RUNTIME)  //說明這個註解可以保留到執行時
@Target(ElementType.TYPE) //說明這個註解只能用在型別上面,包括類,介面,列舉等
@interface MyAnnotation{
	//配置引數,如果只有一個配置引數,並且名稱是value,在賦值時可以省略value=
	String value();
}

//使用註解
@MyAnnotation("/login")
class MyClass{
	
}

16.3.7 獲取內部類或外部類資訊

public Class<?>[] getClasses():返回所有公共內部類和內部介面。包括從超類繼承的公共類和介面成員以及該類宣告的公共類和介面成員。

public Class<?>[] getDeclaredClasses():返回 Class 物件的一個數組,這些物件反映宣告為此 Class 物件所表示的類的成員的所有類和介面。包括該類所宣告的公共、保護、預設(包)訪問及私有類和介面,但不包括繼承的類和介面。

public Class<?> getDeclaringClass():如果此 Class 物件所表示的類或介面是一個內部類或內部介面,則返回它的外部類或外部介面,否則返回null。

	@Test
	public void test5(){
		Class<?> clazz = Map.class;
		Class<?>[] inners = clazz.getDeclaredClasses();
		for (Class<?> inner : inners) {
			System.out.println(inner);
		}
		
		Class<?> ec = Map.Entry.class;
		Class<?> outer = ec.getDeclaringClass();
		System.out.println(outer);
	}

16.3.8 動態建立和操作任意型別的陣列

在java.lang.reflect包下還提供了一個Array類,Array物件可以代表所有的陣列。程式可以通過使用Array類來動態的建立陣列,運算元組元素等。

Array類提供瞭如下幾個方法:

public static Object newInstance(Class<?> componentType, int… dimensions):建立一個具有指定的元件型別和維度的新陣列。

public static void setXxx(Object array,int index,xxx value):將array陣列中[index]元素的值修改為value。此處的Xxx對應8種基本資料型別,如果該屬性的型別是引用資料型別,則直接使用set(Object array,int index, Object value)方法。

public static xxx getXxx(Object array,int index,xxx value):將array陣列中[index]元素的值返回。此處的Xxx對應8種基本資料型別,如果該屬性的型別是引用資料型別,則直接使用get(Object array,int index)方法。

import java.lang.reflect.Array;

public class TestArray {
	public static void main(String[] args) {
		Object arr = Array.newInstance(String.class, 5);
		Array.set(arr, 0, "尚矽谷");
		Array.set(arr, 1, "佟剛");
		System.out.println(Array.get(arr, 0));
		System.out.println(Array.get(arr, 1));
		System.out.println(Array.get(arr, 2));
	}
}