今天項目中遇到了一個問題,要調用一個類,並擷取這個類的屬性進行賦值然後將這個類傳遞到方法中做為參數。
實際操作時才發現,這個類中的欄位屬性是私人的,不能進行賦值!沒有提供公有的方法。而這個類又是打包成jar給我的,我還不能更改它的代碼,以至於想手動給它寫個set方法都是問題。後來想到用反射可以解決這個問題,於是試了一下,果然!
反射看來根本不區分是否是private的,調用本身的私人方法是可以的,但是調用父類的私人方法則不行,糾其原因很有可能是因為getDeclaredMethod方法和getMethod方法並不會尋找父類的私人方法,自己寫遞迴可以解決,不過利用反射來做的話效能不會太好。
我們來看下面這個代碼。
Field[] fields = obj.getDeclaredFields();for (int i = 0; i < fields.length; i++) {fields[i].setAccessible(true);for (int j = 0; j < args.length; j++) {String str = args[j];String strs[] = str.split(",");if (strs[0].equals(fields[i].getName())) {fields[i].set(object, strs[1]);break;}}}
fields[i].setAccessible(true);
這句話是關鍵。看它的表面英文意思是設定可進入可訪問為:true。編程意思大家猜想也應該知道了。
通過查看JDK的源碼:
public void setAccessible(boolean flag) throws SecurityException {SecurityManager sm = System.getSecurityManager();if (sm != null) sm.checkPermission(ACCESS_PERMISSION);setAccessible0(this, flag); }
我們可以看到它是通過SecurityManager來系統管理權限的,我們可以啟用java.security.manager來判斷程式是否具有調用setAccessible()的許可權。預設情況下,核心API和擴充目錄的代碼具有該許可權,而類路徑或通過URLClassLoader載入的應用程式不擁有此許可權。例如:當我們以這種方式來執行上述程式時將會拋出異常
java.security.AccessControlException: access denied
一般情況下,我們並不能對類的私人欄位進行操作,但有的時候我們又必須有能力去處理這些欄位,這時候,我們就需要調用AccessibleObject上的setAccessible()方法來允許這種訪問,而由於反射類中的Field,Method和Constructor繼承自AccessibleObject,因此,通過在這些類上調用setAccessible()方法,我們可以實現對這些欄位的操作。
我們來看看這個ACCESS_PERMISSION裡面究竟怎麼處理的:
static final private java.security.Permission ACCESS_PERMISSION =new ReflectPermission("suppressAccessChecks");
尋找JDK協助文檔可以看到詳細解釋:
-
public final class ReflectPermission
-
extends BasicPermission
反射操作的 Permission 類。ReflectPermission 是一種
指定許可權,沒有動作。當前定義的唯一名稱是suppressAccessChecks,它允許取消由反射對象在其使用點上執行的標準 Java 語言訪問檢查 - 對於 public、default(包)訪問、protected、private 成員。
下表提供了允許許可權的簡要說明,並討論了授予代碼許可權的風險。
許可權目標名稱 |
許可權允許的內容 |
允許此許可權的風險 |
suppressAccessChecks |
能夠訪問類中的欄位和調用方法。注意,這不僅包括 public、而且還包括 protected 和 private 欄位和方法。 |
存在的風險是,通常停用資訊(也許是保密資訊)和方法可能會接受惡意代碼訪問。 |
這裡就一點瞭然了。fields.setAccessible(true);的實際作用就是使許可權可以訪問public,protected,private的欄位!
是不是很爽呢。當然這種方法破壞了JAVA原有的許可權體系。所以不到萬不得已,還是少用,反射的效率畢竟不是那麼高滴。
好,知道了這個我們再來寫一個通用的萬能方法,只是傳遞相應的類,欄位名稱和值,我們在方法內部將其反射並進行執行個體化。然後進行相應欄位的賦值。由於我只用到了欄位。你可以加上其它的東東。嗯。這個好玩。
package unit.sms;public class Smss {private String destID;private String content;private String mobile;public String getDestID() {return destID;}public String getContent() {return content;}public String getMobile() {return mobile;}}
package com.sinoglobal.utils;import java.lang.reflect.Field;import com.jasson.mas.api.smsapi.Sms;/** * 反射的通用工具類 * * @author lz * */public class ReflectionUtils {/** * 用於對類的欄位賦值,無視private,project修飾符,無視set/get方法 * @param c 要反射的類 * @param args 類的欄位名和值 每個欄位名和值用英文逗號隔開 * @return */@SuppressWarnings("unchecked")public static Object getInstance(Class c, String... args) {try {Object object = Class.forName(c.getName()).newInstance();Class<?> obj = object.getClass();Field[] fields = obj.getDeclaredFields();for (int i = 0; i < fields.length; i++) {fields[i].setAccessible(true);for (int j = 0; j < args.length; j++) {String str = args[j];String strs[] = str.split(",");if (strs[0].equals(fields[i].getName())) {fields[i].set(object, strs[1]);break;}}}return object;} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}return null;}public static void main(String[] args) {Object object = getInstance(Smss.class, "destID,01201101", "mobile,15810022404", "content,測試資料。"); Smss sms = (Smss) object; System.out.println("簡訊內容:" + sms.getContent()); System.out.println("手機號碼:" + sms.getMobile()); System.out.println("尾號:" + sms.getDestID());}}
控制台輸出:
簡訊內容:測試資料。
手機號碼:15810022404
尾號:01201101
fields.setAccessible(true);的使用可能大家都會,但我們要做的是,知其然,知其所以然。
看JDK的源碼,無疑是學習和解決此方法的最佳途徑。
over~~~