熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Java反射訪問私有變量和私有方法

2022-06-13   來源: Java核心技術 

  引言

  對於軟件開發人員來說單元測試是一項必不可少的工作它既可以驗證程序的有效性又可以在程序出現 BUG 的時候幫助開發人員快速的定位問題所在但是在寫單元測試的過程中開發人員經常要訪問類的一些非公有的成員變量或方法這給測試工作帶來了很大的困擾本文總結了訪問類的非公有成員變量或方法的四種途徑以方便測試人員在需要訪問類非公有成員變量或方法時進行選擇

  盡管有很多經驗豐富的程序員認為不應該提倡訪問類的私有成員變量或方法因為這樣做違反了 Java 語言封裝性的基本規則然而在實際測試中被測試的對象千奇百怪為了有效快速的進行單元測試有時我們不得不違反一些這樣或那樣的規則本文只討論如何訪問類的非公有成員變量或方法至於是否應該在開發測試中這樣做則留給讀者自己根據實際情況去判斷和選擇

  方法一修改訪問權限修飾符

  先介紹最簡單也是最直接的方法就是利用 Java 語言自身的特性達到訪問非公有成員的目的說白了就是直接將 private 和 protected 關鍵字改為 public 或者直接刪除我們建議直接刪除因為在 Java 語言定義中缺省訪問修飾符是包可見的這樣做之後我們可以另建一個源碼目錄 —— test 目錄(多數 IDE 支持這麼做如 Eclipse 和 JBuilder)然後將測試類放到 test 目錄相同包下從而達到訪問待測類的成員變量和方法的目的此時在其它包的代碼依然不能訪問這些變量或方法在一定程度上保障了程序的封裝性

  下面的代碼示例展示了這一方法

  清單 原始待測類 A 代碼

  public class A {    private String name = null;    private void calculate() {    }}

  清單 針對單元測試修改後的待測類 A 的代碼

  public class A {    String name = null;    private void calculate() {    }}

  這種方法雖然看起來簡單粗暴但經驗告訴我們這個方法在測試過程中是非常有效的當然由於改變了源代碼雖然只是包可見也已經破壞了對象的封裝性對於多數對代碼安全性要求嚴格的系統此方法並不可取

  方法二利用安全管理器

  安全性管理器與反射機制相結合也可以達到我們的目的Java 運行時依靠一種安全性管理器來檢驗調用代碼對某一特定的訪問而言是否有足夠的權限具體來說安全性管理器是 javalangSecurityManager 類或擴展自該類的一個類且它在運行時檢查某些應用程序操作的權限換句話說所有的對象訪問在執行自身邏輯之前都必須委派給安全管理器當訪問受到安全性管理器的控制應用程序就只能執行那些由相關安全策略特別准許的操作因此安全管理器一旦啟動可以為代碼提供足夠的保護默認情況下安全性管理器是沒有被設置的除非代碼明確地安裝一個默認的或定制的安全管理器否則運行時的訪問控制檢查並不起作用我們可以通過這一點在運行時避開 Java 的訪問控制檢查達到我們訪問非公有成員變量或方法的目的為能訪問我們需要的非公有成員我們還需要使用 Java 反射技術Java 反射是一種強大的工具它使我們可以在運行時裝配代碼而無需在對象之間進行源代碼鏈接從而使代碼更具靈活性在編譯時Java 編譯程序保證了私有成員的私有特性從而一個類的私有方法和私有成員變量不能被其他類靜態引用然而通過 Java 反射機制使得我們可以在運行時查詢以及訪問變量和方法由於反射是動態的因此編譯時的檢查就不再起作用了

  下面的代碼演示了如何利用安全性管理器與反射機制訪問私有變量

  清單 利用反射機制訪問類的成員變量

  //獲得指定變量的值

  public static Object getValue(Object instance String fieldName)

  throws   IllegalAccessException NoSuchFieldException {

  Field field = getField(instancegetClass() fieldName);

  // 參數值為true禁用訪問控制檢查

  fieldsetAccessible(true);

  return fieldget(instance);

  }

  //該方法實現根據變量名獲得該變量的值

  public static Field getField(Class thisClass String fieldName)

  throws NoSuchFieldException {

  if (thisClass == null) {

  throw new NoSuchFieldException(Error field !);

  }

  }

  其中 getField(instancegetClass() fieldName) 通過反射機制獲得對象屬性如果存在安全管理器方法首先使用 this 和 MemberDECLARED 作為參數調用安全管理器的 checkMemberAccess 方法這裡的 this 是 this 類或者成員被確定的父類 如果該類在包中那麼方法還使用包名作為參數調用安全管理器的 checkPackageAccess 方法 每一次調用都可能導致 SecurityException當訪問被拒絕時這兩種調用方式都會產生 securityexception 異常

  setAccessible(true) 方法通過指定參數值為 true 來禁用訪問控制檢查從而使得該變量可以被其他類調用我們可以在我們所寫的類中擴展一個普通的基本類 javalangreflectAccessibleObject 類這個類定義了一種 setAccessible 方法使我們能夠啟動或關閉對這些類中其中一個類的實例的接入檢測這種方法的問題在於如果使用了安全性管理器它將檢測正在關閉接入檢測的代碼是否允許這樣做如果未經允許安全性管理器拋出一個例外

  除訪問私有變量我們也可以通過這個方法訪問私有方法

  清單 利用反射機制訪問類的成員方法

  public static Method getMethod(Object instance String methodName Class[] classTypes)

  throws   NoSuchMethodException {

  Method accessMethod = getMethod(instancegetClass() methodName classTypes);

  //參數值為true禁用訪問控制檢查

  accessMethodsetAccessible(true);

  return accessMethod;

  }

  private static Method getMethod(Class thisClass String methodName Class[] classTypes)

  throws NoSuchMethodException {

  if (thisClass == null) {

  throw new NoSuchMethodException(Error method !);

  } try {

  return thisClassgetDeclaredMethod(methodName classTypes);

  } catch (NoSuchMethodException e) {

  return getMethod(thisClassgetSuperclass() methodName classTypes);

  }

  }

  獲得私有方法的原理與獲得私有變量的方法相同當我們得到了函數後需要對它進行調用這時我們需要通過 invoke() 方法來執行對該函數的調用代碼示例如下

  //調用含單個參數的方法

  public static Object invokeMethod(Object instance String methodName Object arg)

  throws NoSuchMethodException

  IllegalAccessException InvocationTargetException {

  Object[] args = new Object[];

  args[] = arg;

  return invokeMethod(instance methodName args);

  }

  //調用含多個參數的方法

  public static Object invokeMethod(Object instance String methodName Object[] args)

  throws NoSuchMethodException

  IllegalAccessException InvocationTargetException {

  Class[] classTypes = null;

  if (args != null) {

  classTypes = new Class[argslength];

  for (int i = ; i < argslength; i++) {

  if (args[i] != null) {

  classTypes[i] = args[i]getClass();

  }

  }

  }

  return getMethod(instance methodName classTypes)invoke(instance args);

  }

  利用安全管理器及反射可以在不修改源碼的基礎上訪問私有成員為測試帶來了極大的方便尤其是在編譯期間該方法可以順利地通過編譯但同時該方法也有一些缺點第一個是性能問題用於字段和方法接入時反射要遠慢於直接代碼第二個是權限問題有些涉及 Java 安全的程序代碼並沒有修改安全管理器的權限此時本方法失效

  另一種方法

  package test;

  import javalangreflectField;

  import modelDept;

  public class TypeTest {

  public static void main(String args[])

  {

  Dept d=new Dept();

  dsetDeptNo();

  dsetDName(v);

  dsetLoc(mopish);

  delete(d Deptclass);

  }

  public static void delete(Object obj Class<?> clazz)

  {

  try

  {

  Systemoutprintln(?+(obj instanceof Dept));

  Systemoutprintln(clazzgetName());

  Systemoutprintln(clazzgetDeclaredFields()length);

  for(Field f: clazzgetDeclaredFields())

  {

  fsetAccessible(true);

  Systemoutprintln(fgetName());

  Systemoutprintln(+fget(obj));

  }

  }catch(Exception e)

  {

  eprintStackTrace();

  }

  }

  }

  package model;

  public class Dept {

  private long deptNo;

  private String DName;

  private String Loc;

  public long getDeptNo() {

  return deptNo;

  }

  public void setDeptNo(long deptNo) {

  thisdeptNo = deptNo;

  }

  public String getDName() {

  return DName;

  }

  public void setDName(String dName) {

  DName = dName;

  }

  public String getLoc() {

  return Loc;

  }

  public void setLoc(String loc) {

  Loc = loc;

  }

  }


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26190.html
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.