利用反射对私有属性/方法进行设置/调用(转自己贴)

来源:互联网 发布:我的兄弟是逗比网络剧 编辑:程序博客网 时间:2024/05/17 08:04
因一时兴起看了一些有关 Java 反射( Reflection )的东西。以下要说明的问题是如何直接对某个特定类的私有属性( private field )不使用其暴露的 set 方法而是直接进行设值操作,或调用类的私有方法( private method )。

首先要说明的是,这在 java 里是允许这么做的。虽然这样直接访问私有属性或调用私有方法,会破坏了 OO 的一大基本原则:封装,但 Java 里是千真万确的提供了这么做的基础的。一些 Open source framework 的“豪华”功能也是依赖于此的。

此前在网上看到不少兄弟提出过这样的问题,有人略带讽刺的回复说这样做是不可以的。在这里不才给出一个简单的示例来说明如何完成的这个被看成 Mission Imposable 的。

首先我们建立一个测试用的类( TargetClass ):

 

  1. package org.rossalee.test; 
  2. public class TargetClass { 
  3.     public String name ; 
  4.     private String age ; 
  5.     public TargetClass() { 
  6.        super(); 
  7.     } 
  8.     public void showName() { 
  9.        System.out.println(name); 
  10.     } 
  11.     private void showAge() { 
  12.        System.out.println(age); 
  13.     } 

这个目标类里有一个 public 权限 String 类型 name 属性和一个 private 权限 String 类型的“ age ”属性,以及一个 public 方法“ showName() ”和 private 方法“ showAge() ”。

一般来说我们是可以直接操作该类的 name 属性或调用 showName() 方法的。这都是很正常。但如果想要对 age 属性进行设值的话,那就只能依靠 TargeClass 提供一个修饰为 public setAge(String age) 的设值方法,才能进行设值。但我们上面这个类里并没有提供,所以那这个 age 属性基本上是没有办法进行再设值的了。同样,我们也不可以调 TargetClass 类的 shwoAge() 方法。因为他也是 private 的。

但我们使用 Java Core API 提供的 Reflect 就可以完成这个功能。

以下是实现对私有属性进行操作的基本步骤:

一、              使用 Class 对象提供的 static 方法 forName() 方法,将类进行加载,成为 Java 里一种特殊的对象——类对象。

二、              使用 Class 对象提供的方法 getDeclaredFields() ,此方法   返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。即有公共的也有私有的。

三、              调用对于要进行操作的 field 对象的 setAccessible() 。参数传入“ true ”。此方法从 java.lang.reflect.AccessibleObject 对象中继承而来。执行该方法后,就可以对 age 属性进行操作了。但设值是要有特定的方法来实现的。另外,还有一点要说明的是,此时加载的类对象是没有实例化的,所以最终我们要进行设值的并不是这个类对象,而 TargetClass 类型的对象。这个对象你可以自己 new 出来,也可以通过调用类对象的 newInstance() 方法实例出来。在这里我们暂定这个 new 出来的 TargetClass 对象为 tc ,即:

TargetClass tc=new TargetClass()

四、              这时我们要用 Field 对象提供的 set(Object obj, Object value) 方法进行设置。这个方法有两个参数: obj 就传入我们在一步 new 出来的 tc Value 就传入 age 所属类型的值,这里的 age String 类型,这里传一个 String 进来就可以了。

对于私有方法的调用也基本相同,但只是最后使用的是 Method 对象的 invoke(Object obj, Object... args) 方法。 Obj 一样也是 tc args 则是 showAge() 方法的参数,这里是无参的,所以传一个 null 进去就可以了。

实现代码如下:

 

  1. package org.rossalee;
  2. import java.lang.reflect.Field;
  3. import java.lang.reflect.InvocationTargetException;
  4. import java.lang.reflect.Method;
  5. import org.rossalee.test.TargetClass;
  6. public class TestMain {
  7.     public static void main(String[] args) {
  8.         Class clazz = null;
  9.         try {
  10.             clazz = Class.forName("org.rossalee.test.TargetClass");
  11.         } catch (ClassNotFoundException e) {
  12.             e.printStackTrace();
  13.         }
  14.         Field[] f = clazz.getDeclaredFields();
  15.         for (int i = 0; i < f.length; i++) {
  16.             System.out.println("Field " + i + " : " + f[i].getName());
  17.         }
  18.         Method[] m = clazz.getDeclaredMethods();
  19.         for (int i = 0; i < m.length; i++) {
  20.             System.out.println("Method " + i + " : " + m[i].getName());
  21.         }
  22.         TargetClass tc = null;
  23.         try {
  24.             tc = (TargetClass) clazz.newInstance();
  25.         } catch (InstantiationException e) {
  26.             e.printStackTrace();
  27.         } catch (IllegalAccessException e) {
  28.             e.printStackTrace();
  29.         }
  30.         f[1].setAccessible(true);
  31.         try {
  32.             f[1].set(tc, "kenshin");
  33.         } catch (IllegalArgumentException e) {
  34.             e.printStackTrace();
  35.         } catch (IllegalAccessException e) {
  36.             e.printStackTrace();
  37.         }
  38.         m[1].setAccessible(true);
  39.         try {
  40.             m[1].invoke(tc, null);
  41.         } catch (IllegalArgumentException e) {
  42.             e.printStackTrace();
  43.         } catch (IllegalAccessException e) {
  44.             e.printStackTrace();
  45.         } catch (InvocationTargetException e) {
  46.             e.printStackTrace();
  47.         }
  48.     }
  49. }