1. 程式人生 > 其它 >Java 反射機制詳解

Java 反射機制詳解

Class類簡介:

Class物件

虛擬機器在class檔案的載入階段,把類資訊儲存在方法區資料結構中,並在Java堆中生成一個Class物件,作為類資訊的入口。

宣告兩個類,Cat.java 和 Dog.java

class Cat {
    private String name;
    private int age;
    static {
        System.out.println("Cat is load");
    }
}

class Dog {
    private String name;
    private int age;
    static {
        System.out.println("Dog is load");
    }
}

獲取Class物件一般有三種方式:

  1. 通過例項變數方式 public class test { public static void main(String[] args) { Dog dog = new Dog(); Class clazz = dog.getClass(); } }
  2. 通過類名方式 public class test { public static void main(String[] args) { Class clazz = Dog.class; } } 通過這種方式時,只會載入Dog類,並不會觸發其類構造器的初始化。
  3. 通過Class.forName(String classname)方式 public class ClassTest { public static void main(String[] args) { try { Class clazz = Class.forName("zzzzzz.Dog"); } catch (ClassNotFoundException e) {} } } 在JDK原始碼實現中,forName方法會呼叫Native方法forName0(),它在JVM中呼叫findClassFromClassLoader()載入Dog類,其原理和ClassLoader一樣,將會觸發Dog類的類構造器初始化,forName0方法宣告如下: private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) 其中initialize引數,用來告訴虛擬機器是否需要對載入的類進行初始化,如果initialize為false,則不會進行初始化Dog類。 Class clazz = Class.forName("zzzzzz.Dog", false, Dog.class.getClassLoader());

反射機制

反射機制reflect可以在執行期間獲取類的欄位、方法、父類和介面等資訊。 1、獲取類欄位

Class class_dog = Dog.class;
Field[] fields = class_dog.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

2、獲取類方法

Class class_dog = Dog.class;
Method[] methods = class_dog.getDeclaredMethods();
for (Method method : methods) {
    System.out.println(method);
}

通過method.invoke(obj, ...args)可以呼叫obj例項的method方法。

3、獲取對應的例項構造器,並生成類例項

public class ClassTest {
    public static void main(String[] args) throws NoSuchMethodException {
        Class class_dog = Dog.class;
        Constructor constructor = class_dog.getConstructor(String.class, int.class);
        constructor.newInstance("Tom", 10);
    }
}

class Dog {
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

如果沒有顯示的宣告預設構造器,class_dog.getConstructor()會丟擲NoSuchMethodException異常。

4、通過newInstance()方法生成類例項

Class class_dog = Dog.class;
Dog dog = class_dog.newInstance();

5、設定私有變數

Class class_dog = Dog.class;
Field name = class_dog.getDeclaredField("name");
name.setAccessible(true);
Dog dog = (Dog) class_dog.newInstance();
name.set(dog, "Tom");

6、獲取私有變數

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe)f.get(null);

這種方式在使用Unsafe類進行黑魔法時經常用到。

反射的效能問題

Stackoverflow上,很多人覺得使用反射reflect會影響系統性能,主要有以下幾點看法: 1、程式碼的驗證防禦邏輯過於複雜,本來這塊驗證時在連結階段實現的,使用反射reflect時需要在執行時進行; 2、產生過多的臨時物件,影響GC的消耗; 3、由於缺少上下文,導致不能進行更多的優化,如JIT;

不過現代JVM已經執行的足夠快,我們應該把主要重心放在複雜的程式碼邏輯上,而不是一開始就進行各種效能優化。

1.    Java的反射機制

(1). 反射和類的關係

在程式執行狀態中,對任意一個類 (指的是.class檔案),都能夠知道這個類所有的屬性和方法

(2). 反射和類物件的關係

       反射對於某個類的一個物件,都能夠呼叫它的任意一個方法屬性

(3). Java反射機制 (基於 (1) 和(2))

[1]. 這種動態獲取類的資訊以及動態地呼叫類物件的方法或者屬性功能稱為Java語言的反射機制。

[2]. 通俗描述反射機制能動態獲取物件的資訊就稱為反射

(4). Java反射機制的好處

極大地提高了應用程式擴充套件性

[1]. 通過多型提高程式的擴充套件性弊端

反射以前提高程式的擴充套件性是通過多型:將子類物件傳遞給父類引用來實現的

e.g. Animal ani=new Cat();

【缺點】必須要通過new來建立子類物件。子類物件必須寫死在程式碼中。

[2]. 有了反射之後,可以通過反射技術省略掉new子類物件的一步

直接將子類物件類名以字串的形式傳遞給反射技術框架由反射技術框架建立這個字串代表的類的例項

(5). 反射的另類理解

[1]. 反射就是把Java中的類中的各個成分對映成相應的類

[2]. 一個Java類組成

成員變數成員方法構造方法修飾符、包等。

[3]. Java中的一個指定的類中的每一個成員都可以用相應的Java反射類API一個例項來表示

2.    Java反射的應用場景

1). 場景I

(1). 有應用程式,但沒有原始碼

一個做好應用程式沒有原始碼。但是現在客戶端想為這個應用程式新增自己的新功能,怎麼辦?存在以下兩個需要解決的問題:

問題[1]. 也就是已經獨立執行的App怎麼識別客戶端自定義的類?(因為App在開發的時候,並不知道客戶端的自定義類是什麼樣子)

問題[2]. 如果App有辦法識別這個了客戶端自定義的類,如何使用這個類的物件呢?(因為App的原始碼不能改變)

(2). 問題I的解決辦法

[1]. 通常一個應用程式為了擴充套件性,都會對外暴露一個介面

[2]. 這個介面由想擴充套件軟體功能的客戶端進行實現,之後,該應用軟體可以使用符合自己條件的介面的子類物件

[3]. 以一個圖的形式表現出來:

[4]. 解決完問題I,問題II產生了

{1}. 客戶端建立了實現了App提供的對外介面Inter的實現子類DemoImpl

{2}. 出現的問題

此時:客戶端想把Interin =new DemoImpl();加入到App中去。但是不能修改原始碼,那怎麼能讓App使用客戶端建立的子物件呢?

(3). 問題II的解決辦法

[1]. 通常應用程式在對外暴露介面之外,還對外提供配置檔案。提供了配置檔案之後,便可以把客戶端自己建立的並且符合標準的類告知應用程式的App,而不用瞭解應用程式App裡面是如何建立這個類的物件的。(從客戶端的角度)

[2]{1}. 應用程式App應該做的就是採用IO流技術讀取配置檔案。這樣,應用程式App便了解到了客戶端自定義的類是什麼。

[2]{2}. 然後應用程式App根據從IO流獲取到的客戶端自定義的類名字串去尋找相應的.class類檔案

如圖:

[2]{3}一旦App找到客戶端自定義的類檔案DemoImpl.class,App可以將其載入到記憶體中,並通過位元組碼檔案建立物件【這一步使用到了Java的反射技術!!!】

【分析為什麼是反射機制】由於App一定是在客戶端使用它之前編寫成功,所以,App本身並不能知道未來客戶端定義的類的名字,所以,一定是在執行時獲取一個類的資訊、獲取這個類的物件並呼叫這個物件的有關方法,這便是Java的反射機制的定義

(4). 如何為自行開發的軟體提高可擴充套件性

[1]. 編寫軟體的時候,要先通過反射技術去實現如何通過類名字串獲取該物件的例項,並且能進行方法呼叫

[2]. 然後對外暴露符合自身軟體規範介面

[3]. 提供符合讀寫規則配置檔案

【知識點回顧】

hibernate如何識別使用者自定義的實體檔案?Struts2如何識別使用者自定義的Action的子類檔案?【都是通過反射技術來實現的】

3.    反射機制的典型應用---Tomcat伺服器

1). Tomcat伺服器應用到的Java的三大技術

IO技術ServerSocket技術反射技術

2). Tomcat伺服器大致處理使用者應答的思路

(1). 對外暴露介面---->著名的Servlet (伺服器指令碼片段)

[1]. 對外提供介面的原因具體處理客戶端應答請求的方式不一樣的。應該根據具體的請求來進行具體的處理。向上抽取形成Servlet介面並提供給客戶端使用。

[2]. 由開發者來實現Servlet介面定義的具體應答請求的處理方式

(2). 提供配置檔案---->web.xml(WEB巨集觀部署描述檔案)

每個Web應用程式都有自己的配置檔案web.xml告知Tomcat伺服器(App)有哪些使用者自定義的Servlet實現類

3). Tomcat具體載入處理細節

(1). Tomcat (App)首先讀取配置檔案web.xml中配置好的Servlet的子類名稱

(2). Tomcat根據讀取到的客戶端實現的Servlet子類的類名字串去尋找對應的位元組碼檔案。如果找到就將其載入到記憶體。

(3). Tomcat通過預先設定好的Java反射處理機制解析位元組碼檔案並建立相應的例項物件。之後呼叫所需要的方法。

【最後】Tomcat一啟動,使用者自定義的Servlet的子類通過Tomcat內部的反射框架也隨之執行。

4.    總結

(1). 反射技術提高了應用程式的可擴充套件性

(2). 反射技術應用起來非常簡單。為使用者和App之間提供可以互動的配置檔案介面

【使用者面對配置檔案的難度<<面對原始碼的難度】

(3). 反射一般是“介面+配置檔案”這種開發形式十分常見

(4). 學習框架技術的要領:學習框架的用途配置檔案

5、例項

(1)一個簡單例子,在系統開發中,除錯程式時我們需要知道類物件中各個成員的值,這明顯便是toString()函式的功能,但在大型系統中,不可能每個實體類中都去重寫toString(),這個時候“反射”便派上用場了,我們可以實現一個輔助類StringSupport,在StringSupport中通過反射獲取所有成員值。

         一個簡單得不敢直視的例子:

public String toString() {
 
		Field fields[] = this.getClass().getDeclaredFields();
 
		StringBuffer buf = new StringBuffer();
		int index = 0;
		for (Field field : fields) {
			if (!field.isAccessible()) {
				System.out.println("false");
				field.setAccessible(true);
			}
			try {
				if (index++ != 0)
					buf.append(",");
				buf.append(field.get(this));
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
 
		}
		return buf.toString();
}

純示例,在實際實現時,要考慮集合物件等各種東西。不過,如果你碰巧有這種需求,只需藉助apache common庫中的lang即可,其已經有很好的實現。

(2)第二個小例子自然是動態代理了,在介紹Java的動態代理前,先來看看設計模式中的代理模式,代理模式的UML圖如下,其思想是為目標物件提供一種代理以控制對這個物件的訪問。主要應用於Client端不能直接訪問目標物件的情形,通過在客戶端和目標物件之間充當中介的代理物件,可以控制Client對目標物件訪問,亦或是新增額外的處理邏輯,論壇中使用者許可權的控制就是一個典型的例子。

        Java的代理機制與其思想是一致的。只不過這裡Proxy是Java為你動態生成的,這個過程藉助java.lang.reflect.Proxy類與java.lang.reflect.InvocationHandler介面進行實現,InvocationHandler介面中invoke函式便是呼叫目標類實現的功能介面的地方,可以在這個地方進行訪問控制,新增額外的處理邏輯。

        使用Java動態代理的方式有兩種:

第一種:

(1)通過實現InvocationHandler介面建立自己的呼叫處理器(InvocationHandler);

(2)通過為Proxy類指定ClassLoader物件和一組interface(也即客戶端要使用的功能介面)來建立動態代理類;

(3)通過反射機制獲得動態代理類的建構函式,其唯一引數型別是呼叫處理器介面型別;

(4)通過建構函式建立動態代理類例項,構造時呼叫處理器物件作為引數被傳入。

第二種:

通過Proxy類的newProxyInstance函式來簡化第一種的流程,在newProxyInstance中包含了上面(2)(3)(4)三步所做的內容。

接下來,又是一個簡單的例子。

功能介面:

public interface Chatroom {
	abstract public void createChat();
}

目標類:

public class ChatroomImpl implements Chatroom {
 
	public void createChat(){
		System.out.println("建立一個聊天室");
	}
}

InvocationHandler實現:

public class ChatInvocationHandler implements InvocationHandler {
 
	private static int count = 0;
 
	private Object target;
 
	public ChatInvocationHandler(Object target){
		this.target = target;
	}
 
	public void setTarget(Object target){
		this.target = target;
	}
 
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
 
		if(count++%2==0){
			return method.invoke(target, args);
		}
 
		System.out.println("屌絲不能建立聊天室");
 
		return null;
	}
 
}

兩種呼叫方式:

(1)

ChatInvocationHandler chatHandler = new ChatInvocationHandler(
					new ChatroomImpl());
 
			Class pClass = Proxy.getProxyClass(Chatroom.class.getClassLoader(),
					new Class[] { Chatroom.class });
 
			Constructor constructor = pClass
					.getConstructor(new Class[] { InvocationHandler.class });
 
			Chatroom proxy = (Chatroom) constructor
					.newInstance(new Object[] { chatHandler });
 
			for (int i = 0; i &lt; 10; i++) {
				if (proxy != null)
					proxy.createChat();
			}

 (2)

ChatInvocationHandler chatHandler = new ChatInvocationHandler(
					new ChatroomImpl());
 
			Chatroom proxy = (Chatroom) Proxy.newProxyInstance(Chatroom.class
					.getClassLoader(), new Class[] { Chatroom.class },
					chatHandler);
 
			for (int i = 0; i &lt; 10; i++) {
				if (proxy != null)
					proxy.createChat();
			}

執行後,便可看到交錯的“建立聊天室”與“屌絲不能建立聊天室”。

         暫到此,後續打算針對Spring AOP中動態代理的使用進行分析,此過程還會涉及Java位元組碼操縱利器”ASM”。Java的Proxy只支援目標類實現了介面的情況,對於沒有介面的情況,Spring通過cglib庫來生成動態代理類,cglib使用了asm的位元組碼操縱能力。

6、應用情景:

情景一:載入資料庫驅動的時候

Class.forName的一個很常見的用法是在載入資料庫驅動的時候。

如:

Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");  
Connection con=DriverManager.getConnection("jdbc:sqlserver://localhost:1433;DatabaseName==JSP","jph","jph");      

為什麼在我們載入資料庫驅動包的時候有的卻沒有呼叫newInstance( )方法呢?

即有的jdbc連線資料庫的寫法裡是Class.forName(xxx.xx.xx);而有一些:Class.forName(xxx.xx.xx).newInstance(),為什麼會有這兩種寫法呢? 

剛才提到,Class.forName("");的作用是要求JVM查詢並載入指定的類,如果在類中有靜態初始化器的話,JVM必然會執行該類的靜態程式碼段。

而在JDBC規範中明確要求這個Driver類必須向DriverManager註冊自己,即任何一個JDBCDriver的Driver類的程式碼都必須類似如下:  public classMyJDBCDriver implements Driver {

static{

                DriverManager.registerDriver(new MyJDBCDriver());

           }

 } 

  既然在靜態初始化器的中已經進行了註冊,所以我們在使用JDBC時只需要Class.forName(XXX.XXX);就可以了。

情景二:使用AIDL與電話管理Servic進行通訊

Method method =Class.forName("Android.os.ServiceManager")

         .getMethod("getService",String.class);

// 獲取遠端TELEPHONY_SERVICE的IBinder物件的代理

IBinder binder =(IBinder) method.invoke(null, new Object[] { TELEPHONY_SERVICE});

// 將IBinder物件的代理轉換為ITelephony物件

ITelephonytelephony = ITelephony.Stub.asInterface(binder);

// 結束通話電話

telephony.endCall();

Refer:

[0] Class.forName()的作用與使用總結

http://blog.csdn.net/fengyuzhengfan/article/details/38086743

[1] 反射--01【反射機制】【反射的應用場景】【Tomcat伺服器】

http://blog.csdn.net/benjaminzhang666/article/details/9408611

[2] Java那點事:反射機制雜談

http://www.jmatrix.org/java/240.html

[3] Java反射機制的適用場景及其利與弊

http://blog.csdn.net/zolalad/article/details/29370565

[4] Class物件和Java反射機制

http://www.importnew.com/21235.html

[5] Class.forName 介紹

http://ludaojuan21.iteye.com/blog/243528

[6] Java深度歷險(七)——Java反射與動態代理

http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy

[7] 說說Java反射機制

http://www.jianshu.com/p/1a21a9cb5bea#