Java高級編程 - 使用反射強制給private字段賦值

        通常狀況下,咱們並不能對類的私有字段進行操做,但有的時候咱們又必須有能力去處理這些字段,這時候,咱們就須要調用AccessibleObject上的setAccessible()方法來容許這種訪問,而因爲反射類中的Field,Method和Constructor繼承自AccessibleObject,所以,經過在這些類上調用setAccessible()方法,咱們能夠實現對這些字段的操做。今天項目中遇到了一個問題,要調用一個類,並獲取這個類的屬性進行賦值而後將這個類傳遞到方法中作爲參數。

        實際操做時才發現,這個類中的字段屬性是私有的,不能進行賦值!沒有提供公有的方法。而這個類又是打包成jar給個人,我還不能更改它的代碼,以致於想手動給它寫個set方法都是問題。後來想到用反射能夠解決這個問題,因而試了一下,果真!java


        反射看來根本不區分是不是private的,調用自己的私有方法是能夠的,可是調用父類的私有方法則不行,糾其緣由頗有多是由於getDeclaredMethod方法和getMethod方法並不會查找父類的私有方法,本身寫遞歸能夠解決,不過利用反射來作的話性能不會太好。

咱們來看下面這個代碼。
shell

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加載的應用程序不擁有此權限。

例如:當咱們以這種方式來執行上述程序時將會拋出異常
api

 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 ReflectPermissionextends BasicPermission

反射操做的 Permission 類。ReflectPermission 是一種指定權限,沒有動做。當前定義的惟一名稱是suppressAccessChecks,它容許取消由反射對象在其使用點上執行的標準 Java 語言訪問檢查 - 對於 public、default(包)訪問、protected、private 成員。

下表提供了容許權限的簡要說明,並討論了授予代碼權限的風險。學習

權限目標名稱
權限容許的內容 容許此權限的風險
suppressAccessChecks 可以訪問類中的字段和調用方法。注意,這不只包括 public、並且還包括 protected 和 private 字段和方法。 存在的風險是,一般不可用的信息(也許是保密信息)和方法可能會接受惡意代碼訪問。

這裏就一點了然了。fields.setAccessible(true);的實際做用就是使權限能夠訪問public,protected,private的字段!

是否是很爽呢。固然這種方法破壞了JAVA原有的權限體系。因此不到萬不得已,仍是少用,反射的效率畢竟不是那麼高滴。

好,知道了這個咱們再來寫一個通用的萬能方法,只是傳遞相應的類,字段名稱和值,咱們在方法內部將其反射並進行實例化。而後進行相應字段的賦值。因爲我只用到了字段。你能夠加上其它的東東。嗯。這個好玩。

測試

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(Sms.class,"destID,01201101","mobile,15810022404","content,測試數據。");  
        Sms sms=(Sms)object;  
        System.out.println("短信內容:"+sms.content);  
        System.out.println("手機號碼:"+sms.mobile);  
        System.out.println("尾號:"+sms.destID);  
    }  
}

控制檯輸出:
this

短信內容:測試數據。
手機號碼:15810022404
尾號:01201101

fields.setAccessible(true);的使用可能你們都會,但咱們要作的是,知其然,知其因此然。

看JDK的源碼,無疑是學習和解決此方法的最佳途徑。
spa

相關文章
相關標籤/搜索