反射實例——JAVA反射機制


反射實例——JAVA反射機制

一、Java反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力。這一概念的提出很快引發了計算機科學領域關於應用反射性的研究。它首先被程序語言的設計領域所採用,並在Lisp和麵向對象方面取得了成績。其中LEAD/LEAD++ 、OpenC++ 、MetaXa和OpenJava等就是基於反射機制的語言。最近,反射機制也被應用到了視窗系統、操作系統和文件系統中。

反射本身並不是一個新概念,它可能會使我們聯想到光學中的反射概念,儘管計算機科學賦予了反射概念新的含義,但是,從現象上來說,它們確實有某些相通之處,這些有助於我們的理解。在計算機科學領域,反射是指一類應用,它們能夠自描述和自控制。也就是說,這類應用通過採用某種機制來實現對自己行為的描述(self-representation)和監測(examination),並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。可以看出,同一般的反射概念相比,計算機科學領域的反射不單單指反射本身,還包括對反射結果所採取的措施。所有采用反射機制的系統(即反射系統)都希望使系統的實現更開放。可以說,實現了反射機制的系統都具有開放性,但具有開放性的系統並不一定採用了反射機制,開放性是反射系統的必要條件。一般來說,反射系統除了滿足開放性條件外還必須滿足原因連接(Causally-connected)。所謂原因連接是指對反射系統自描述的改變能夠立即反映到系統底層的實際狀態和行為上的情況,反之亦然。開放性和原因連接是反射系統的兩大基本要素。

Java中,反射是一種強大的工具。它使您能夠創建靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代表鏈接。反射允許我們在編寫與執行時,使我們的程序代碼能夠接入裝載到JVM中的類的內部信息,而不是源代碼中選定的類協作的代碼。這使反射成為構建靈活的應用的主要工具。但需注意的是:如果使用不當,反射的成本很高。

反射實例——JAVA反射機制

二、Java中的類反射

Reflection 是 Java 程序開發語言的特徵之一,它允許運行中的 Java 程序對自身進行檢查,或者說“自審”,並能直接操作程序的內部屬性。Java 的這一能力在實際應用中也許用得不是很多,但是在其它的程序設計語言中根本就不存在這一特性。例如,Pascal、C 或者 C++ 中就沒有辦法在程序中獲得函數定義相關的信息。

1. 檢測類:

1.1 reflection的工作機制

考慮下面這個簡單的例子,讓我們看看 reflection 是如何工作的。

<code>import java.lang.reflect.*;
public class DumpMethods {
public static void main(String args[]) {
try {
Class c = Class.forName(args[0]);
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
}
catch (Throwable e) {
System.err.println(e);
}
}
}/<code>

按如下語句執行:

<code>java DumpMethods java.util.Stack /<code>

它的結果輸出為:

<code>public java.lang.Object java.util.Stack.push(java.lang.Object) 
public synchronized java.lang.Object java.util.Stack.pop()
public synchronized java.lang.Object java.util.Stack.peek()
public Boolean java.util.Stack.empty()
public synchronized int java.util.Stack.search(java.lang.Object)/<code>

這樣就列出了java.util.Stack 類的各方法名以及它們的限制符和返回類型。

這個程序使用 Class.forName 載入指定的類,然後調用 getDeclaredMethods 來獲取這個類中定義了的方法列表。java.lang.reflect.Methods 是用來描述某個類中單個方法的一個類。

1.2 Java類反射中的主要方法

對於以下三類組件中的任何一類來說 -- 構造函數、字段和方法 -- java.lang.Class 提供四種獨立的反射調用,以不同的方式來獲得信息。調用都遵循一種標準格式。以下是用於查找構造函數的一組反射調用:

  • Constructor getConstructor(Class[] params)——獲得使用特殊的參數類型的公共構造函數,
  • Constructor[] getConstructors()——獲得類的所有公共構造函數
  • Constructor getDeclaredConstructor(Class[] params)——獲得使用特定參數類型的構造函數(與接入級別無關)
  • Constructor[] getDeclaredConstructors()——獲得類的所有構造函數(與接入級別無關)

獲得字段信息的Class 反射調用不同於那些用於接入構造函數的調用,在參數類型數組中使用了字段名:

  • Field getField(String name)——獲得命名的公共字段
  • Field[] getFields()——獲得類的所有公共字段
  • Field getDeclaredField(String name)——獲得類聲明的命名的字段
  • Field[] getDeclaredFields()——獲得類聲明的所有字段

用於獲得方法信息函數:

  • Method getMethod(String name, Class[] params)——使用特定的參數類型,獲得命名的公共方法
  • Method[] getMethods()——獲得類的所有公共方法
  • Method getDeclaredMethod(String name, Class[] params)——使用特寫的參數類型,獲得類聲明的命名的方法
  • Method[] getDeclaredMethods()——獲得類聲明的所有方法

1.3 開始使用 Reflection:

用於 reflection 的類,如 Method,可以在 java.lang.relfect 包中找到。使用這些類的時候必須要遵循三個步驟:第一步是獲得你想操作的類的 java.lang.Class 對象。在運行中的 Java 程序中,用 java.lang.Class 類來描述類和接口等。

下面就是獲得一個 Class 對象的方法之一:

<code>Class c = Class.forName("java.lang.String"); /<code>

這條語句得到一個 String 類的類對象。還有另一種方法,如下面的語句:

<code>Class c = int.class; /<code>

或者

<code>lass c = Integer.TYPE; 
/<code>

它們可獲得基本類型的類信息。其中後一種方法中訪問的是基本類型的封裝類 (如 Integer) 中預先定義好的 TYPE 字段。

第二步是調用諸如 getDeclaredMethods 的方法,以取得該類中定義的所有方法的列表。

一旦取得這個信息,就可以進行第三步了——使用 reflection API 來操作這些信息,如下面這段代碼:

<code>Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[0].toString());/<code>

它將以文本方式打印出 String 中定義的第一個方法的原型。

2. 處理對象:

如果要作一個開發工具像debugger之類的,你必須能發現filed values,以下是三個步驟:

  • 創建一個Class對象
  • 通過getField 創建一個Field對象
  • 調用Field.getXXX(Object)方法(XXX是Int,Float等,如果是對象就省略;Object是指實例).

例如:

<code>import java.lang.reflect.*;
import java.awt.*;
class SampleGet {
public static void main(String[] args) {
Rectangle r = new Rectangle(100, 325);
printHeight(r);
}
static void printHeight(Rectangle r) {
Field heightField;
Integer heightValue;
Class c = r.getClass();
try {
heightField = c.getField("height");
heightValue = (Integer) heightField.get(r);
System.out.println("Height: " + heightValue.toString());
}
catch (NoSuchFieldException e) {
System.out.println(e);
}
catch (SecurityException e) {
System.out.println(e);
}
catch (IllegalAccessException e) {
System.out.println(e);
}
}
}/<code>


反射實例——JAVA反射機制

三、安全性和反射

在處理反射時安全性是一個較複雜的問題。反射經常由框架型代碼使用,由於這一點,我們可能希望框架能夠全面接入代碼,無需考慮常規的接入限制。但是,在其它情況下,不受控制的接入會帶來嚴重的安全性風險,例如當代碼在不值得信任的代碼共享的環境中運行時。

由於這些互相矛盾的需求,Java編程語言定義一種多級別方法來處理反射的安全性。基本模式是對反射實施與應用於源代碼接入相同的限制:

  • 從任意位置到類公共組件的接入
  • 類自身外部無任何到私有組件的接入
  • 受保護和打包(缺省接入)組件的有限接入

不過至少有些時候,圍繞這些限制還有一種簡單的方法。我們可以在我們所寫的類中,擴展一個普通的基本類java.lang.reflect.AccessibleObject 類。這個類定義了一種setAccessible方法,使我們能夠啟動或關閉對這些類中其中一個類的實例的接入檢測。唯一的問題在於如果使用了安全性管理器,它將檢測正在關閉接入檢測的代碼是否許可了這樣做。如果未許可,安全性管理器拋出一個例外。

下面是一段程序,在TwoString 類的一個實例上使用反射來顯示安全性正在運行:

<code>public class ReflectSecurity {
public static void main(String[] args) {
try {
TwoString ts = new TwoString("a", "b");
Field field = clas.getDeclaredField("m_s1");
// field.setAccessible(true);
System.out.println("Retrieved value is " +
field.get(inst));
}
catch (Exception ex) {
ex.printStackTrace(System.out);
}
}
}/<code>

如果我們編譯這一程序時,不使用任何特定參數直接從命令行運行,它將在field .get(inst)調用中拋出一個IllegalAccessException異常。如果我們不註釋field.setAccessible(true)代碼行,那麼重新編譯並重新運行該代碼,它將編譯成功。最後,如果我們在命令行添加了JVM參數-Djava.security.manager以實現安全性管理器,它仍然將不能通過編譯,除非我們定義了ReflectSecurity類的許可權限。

四、反射性能

反射是一種強大的工具,但也存在一些不足。一個主要的缺點是對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於只直接執行相同的操作。

下面的程序是字段接入性能測試的一個例子,包括基本的測試方法。每種方法測試字段接入的一種形式 -- accessSame 與同一對象的成員字段協作,accessOther 使用可直接接入的另一對象的字段,accessReflection 使用可通過反射接入的另一對象的字段。在每種情況下,方法執行相同的計算 -- 循環中簡單的加/乘順序。

程序如下:

<code>public int accessSame(int loops) {
m_value = 0;
for (int index = 0; index < loops; index++) {
m_value = (m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return m_value;
}
public int accessReference(int loops) {
TimingClass timing = new TimingClass();
for (int index = 0; index < loops; index++) {
timing.m_value = (timing.m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return timing.m_value;
}
public int accessReflection(int loops) throws Exception {
TimingClass timing = new TimingClass();
try {
Field field = TimingClass.class.
getDeclaredField("m_value");
for (int index = 0; index < loops; index++) {
int value = (field.getint(timing) +
ADDITIVE_VALUE) * MULTIPLIER_VALUE;
field.setint(timing, value);
}
return timing.m_value;
}
catch (Exception ex) {
System.out.println("Error using reflection");
throw ex;
}
}/<code>

在上面的例子中,測試程序重複調用每種方法,使用一個大循環數,從而平均多次調用的時間衡量結果。平均值中不包括每種方法第一次調用的時間,因此初始化時間不是結果中的一個因素。

結束語

Java語言反射提供一種動態鏈接程序組件的多功能方法。它允許程序創建和控制任何類的對象(根據安全性限制),無需提前硬編碼目標類。這些特性使得反射特別適用於創建以非常普通的方式與對象協作的庫。例如,反射經常在持續存儲對象為數據庫、XML或其它外部格式的框架中使用。Java reflection 非常有用,它使類和數據結構能按名稱動態檢索相關信息,並允許在運行著的程序中操作這些信息。Java 的這一特性非常強大,並且是其它一些常用語言,如 C、C++、Fortran 或者 Pascal 等都不具備的。

但反射有兩個缺點。第一個是性能問題。用於字段和方法接入時反射要遠慢於直接代碼。性能問題的程度取決於程序中是如何使用反射的。如果它作為程序運行中相對很少涉及的部分,緩慢的性能將不會是一個問題。即使測試中最壞情況下的計時圖顯示的反射操作只耗用幾微秒。僅反射在性能關鍵的應用的核心邏輯中使用時性能問題才變得至關重要。

許多應用中更嚴重的一個缺點是使用反射會模糊程序內部實際要發生的事情。程序人員希望在源代碼中看到程序的邏輯,反射等繞過了源代碼的技術會帶來維護問題。反射代碼比相應的直接代碼更復雜,正如性能比較的代碼實例中看到的一樣。解決這些問題的最佳方案是保守地使用反射——僅在它可以真正增加靈活性的地方——記錄其在目標類中的使用。

最近在成都寫一個移動增值項目,俺負責後臺server端。功能很簡單,手機用戶通過GPRS打開Socket與服務器連接,我則根據用戶傳過來的數據做出響應。做過類似項目的兄弟一定都知道,首先需要定義一個類似於MSNP的通訊協議,不過今天的話題是如何把這個系統設計得具有高度的擴展性。由於這個項目本身沒有進行過較為完善的客戶溝通和需求分析,所以以後肯定會有很多功能上的擴展,通訊協議肯定會越來越龐大,而我作為一個不那麼勤快的人,當然不想以後再去修改寫好的程序,所以這個項目是實踐面向對象設計的好機會。

首先定義一個接口來隔離類:

<code>package org.bromon.reflect;
public interface Operator
{
public java.util.List act(java.util.List params)
}/<code>

根據設計模式的原理,我們可以為不同的功能編寫不同的類,每個類都繼承Operator接口,客戶端只需要針對Operator接口編程就可以避免很多麻煩。比如這個類:

<code>package org.bromon.reflect.*;
public class Success implements Operator
{
public java.util.List act(java.util.List params)
{
List result=new ArrayList();
}

}/<code>

Java 的反射機制是使其具有動態特性的非常關鍵的一種機制,也是在JavaBean 中廣泛應用的一種特性。

運用JavaBean 的最常見的問題是:根據指定的類名,類字段名和所對應的數據,得到該類的實例,下面的一個例子演示了這一實現。

<code>-|Base.java //抽象基類 
|Son1.java //基類擴展1
|Son2.java //基類擴展2
|Util.java
/**
* @author metaphy
* create 2005-4-14 9:06:56
* 說明:
*/
(1)Base.java 抽象基類只是一個定義
public abstract class Base {
}
(2)Son1.java /Son2.java 是已經實現的JavaBean
public class Son1 extends Base{
private int id ;
private String name ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void son1Method(String s){
System.out.println(s) ;
}
}

(3)
public class Son2 extends Base{
private int id;
private double salary;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
(4)Util.java 演示瞭如何根據指定的類名,類字段名和所對應的數據,得到一個類的實例
import java.lang.reflect.Method;
public class Util {
//此方法的最大好處是沒有類名Son1,Son2 可以通過參數來指定,程序裡面根本不用出現
public static Base convertStr2ServiceBean(String beanName,String fieldSetter,String paraValue){
Base base = null ;
try {
Class cls = Class.forName(beanName) ;
base = (Base)cls.newInstance() ;
Class[] paraTypes = new Class[]{String.class };
Method method = cls.getMethod(fieldSetter, paraTypes) ;
String[] paraValues = new String[]{paraValue} ;
method.invoke(base, paraValues) ;
}
catch (Throwable e) {
System.err.println(e);
}
return base ;
}
public static void main(String[] args){
Son1 son1 =(Son1) Util.convertStr2ServiceBean("trying.reflect.Son1","setName","wang da sha");
System.out.println("son1.getName() :"+son1.getName()) ;
}
}
//調用結果:
//son1.getName() :wang da sha/<code>

寫在最後

  • 大廠面試真題
  • Java架構師學習思維導圖
  • Java架構師學習視頻
  • 關注,私信我"86"免費獲取


分享到:


相關文章: