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 运行时依靠一种安全性管理器来检验调用代码对某一特定的访问而言是否有足够的权限 具体来说 安全性管理器是 java lang SecurityManager 类或扩展自该类的一个类 且它在运行时检查某些应用程序操作的权限 换句话说 所有的对象访问在执行自身逻辑之前都必须委派给安全管理器 当访问受到安全性管理器的控制 应用程序就只能执行那些由相关安全策略特别准许的操作 因此安全管理器一旦启动可以为代码提供足够的保护 默认情况下 安全性管理器是没有被设置的 除非代码明确地安装一个默认的或定制的安全管理器 否则运行时的访问控制检查并不起作用 我们可以通过这一点在运行时避开 Java 的访问控制检查 达到我们访问非公有成员变量或方法的目的 为能访问我们需要的非公有成员 我们还需要使用 Java 反射技术 Java 反射是一种强大的工具 它使我们可以在运行时装配代码 而无需在对象之间进行源代码链接 从而使代码更具灵活性 在编译时 Java 编译程序保证了私有成员的私有特性 从而一个类的私有方法和私有成员变量不能被其他类静态引用 然而 通过 Java 反射机制使得我们可以在运行时查询以及访问变量和方法 由于反射是动态的 因此编译时的检查就不再起作用了
下面的代码演示了如何利用安全性管理器与反射机制访问私有变量
清单 利用反射机制访问类的成员变量
//获得指定变量的值
public static Object getValue(Object instance String fieldName)
throws IllegalAccessException NoSuchFieldException {
Field field = getField(instance getClass() fieldName);
// 参数值为true 禁用访问控制检查
field setAccessible(true);
return field get(instance);
}
//该方法实现根据变量名获得该变量的值
public static Field getField(Class thisClass String fieldName)
throws NoSuchFieldException {
if (thisClass == null) {
throw new NoSuchFieldException( Error field ! );
}
}
其中 getField(instance getClass() fieldName) 通过反射机制获得对象属性 如果存在安全管理器 方法首先使用 this 和 Member DECLARED 作为参数调用安全管理器的 checkMemberAccess 方法 这里的 this 是 this 类或者成员被确定的父类 如果该类在包中 那么方法还使用包名作为参数调用安全管理器的 checkPackageAccess 方法 每一次调用都可能导致 SecurityException 当访问被拒绝时 这两种调用方式都会产生 securityexception 异常
setAccessible(true) 方法通过指定参数值为 true 来禁用访问控制检查 从而使得该变量可以被其他类调用 我们可以在我们所写的类中 扩展一个普通的基本类 java lang reflect AccessibleObject 类 这个类定义了一种 setAccessible 方法 使我们能够启动或关闭对这些类中其中一个类的实例的接入检测 这种方法的问题在于如果使用了安全性管理器 它将检测正在关闭接入检测的代码是否允许这样做 如果未经允许 安全性管理器抛出一个例外
除访问私有变量 我们也可以通过这个方法访问私有方法
清单 利用反射机制访问类的成员方法
public static Method getMethod(Object instance String methodName Class[] classTypes)
throws NoSuchMethodException {
Method accessMethod = getMethod(instance getClass() methodName classTypes);
//参数值为true 禁用访问控制检查
accessMethod setAccessible(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 thisClass getDeclaredMethod(methodName classTypes);
} catch (NoSuchMethodException e) {
return getMethod(thisClass getSuperclass() 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[args length];
for (int i = ; i < args length; i++) {
if (args[i] != null) {
classTypes[i] = args[i] getClass();
}
}
}
return getMethod(instance methodName classTypes) invoke(instance args);
}
利用安全管理器及反射 可以在不修改源码的基础上访问私有成员 为测试带来了极大的方便 尤其是在编译期间 该方法可以顺利地通过编译 但同时该方法也有一些缺点 第一个是性能问题 用于字段和方法接入时反射要远慢于直接代码 第二个是权限问题 有些涉及 Java 安全的程序代码并没有修改安全管理器的权限 此时本方法失效
另一种方法
package test;
import java lang reflect Field;
import model Dept;
public class TypeTest {
public static void main(String args[])
{
Dept d=new Dept();
d setDeptNo( );
d setDName( v );
d setLoc( mopish );
delete(d Dept class);
}
public static void delete(Object obj Class<?> clazz)
{
try
{
System out println( ? +(obj instanceof Dept));
System out println(clazz getName());
System out println(clazz getDeclaredFields() length);
for(Field f: clazz getDeclaredFields())
{
f setAccessible(true);
System out println(f getName());
System out println( +f get(obj));
}
}catch(Exception e)
{
e printStackTrace();
}
}
}
package model;
public class Dept {
private long deptNo;
private String DName;
private String Loc;
public long getDeptNo() {
return deptNo;
}
public void setDeptNo(long deptNo) {
this deptNo = 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;
}
lishixinzhi/Article/program/Java/hx/201311/26190