当前位置: 首页 > news >正文

Java反射访问私有变量和私有方法

引言

  对于软件开发人员来说,单元测试是一项必不可少的工作。它既可以验证程序的有效性,又可以在程序出现 BUG 的时候,帮助开发人员快速的定位问题所在。但是,在写单元测试的过程中,开发人员经常要访问类的一些非公有的成员变量或方法,这给测试工作带来了很大的困扰。本文总结了访问类的非公有成员变量或方法的四种途径,以方便测试人员在需要访问类非公有成员变量或方法时进行选择。

  尽管有很多经验丰富的程序员认为不应该提倡访问类的私有成员变量或方法,因为这样做违反了 Java 语言封装性的基本规则。然而,在实际测试中被测试的对象千奇百怪,为了有效快速的进行单元测试,有时我们不得不违反一些这样或那样的规则。本文只讨论如何访问类的非公有成员变量或方法,至于是否应该在开发测试中这样做,则留给读者自己根据实际情况去判断和选择。

  方法一:修改访问权限修饰符

  先介绍最简单也是最直接的方法,就是利用 Java 语言自身的特性,达到访问非公有成员的目的。说白了就是直接将 private 和 protected 关键字改为 public 或者直接删除。我们建议直接删除,因为在 Java 语言定义中,缺省访问修饰符是包可见的。这样做之后,我们可以另建一个源码目录 —— test 目录(多数 IDE 支持这么做,如 Eclipse 和 JBuilder),然后将测试类放到 test 目录相同包下,从而达到访问待测类的成员变量和方法的目的。此时,在其它包的代码依然不能访问这些变量或方法,在一定程度上保障了程序的封装性。

  下面的代码示例展示了这一方法。

  清单 1. 原始待测类 A 代码

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

  清单 2. 针对单元测试修改后的待测类 A 的代码

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

  这种方法虽然看起来简单粗暴,但经验告诉我们这个方法在测试过程中是非常有效的。当然,由于改变了源代码,虽然只是包可见,也已经破坏了对象的封装性,对于多数对代码安全性要求严格的系统此方法并不可取。

  方法二:利用安全管理器

  安全性管理器与反射机制相结合,也可以达到我们的目的。Java 运行时依靠一种安全性管理器来检验调用代码对某一特定的访问而言是否有足够的权限。具体来说,安全性管理器是 java.lang.SecurityManager 类或扩展自该类的一个类,且它在运行时检查某些应用程序操作的权限。换句话说,所有的对象访问在执行自身逻辑之前都必须委派给安全管理器,当访问受到安全性管理器的控制,应用程序就只能执行那些由相关安全策略特别准许的操作。因此安全管理器一旦启动可以为代码提供足够的保护。默认情况下,安全性管理器是没有被设置的,除非代码明确地安装一个默认的或定制的安全管理器,否则运行时的访问控制检查并不起作用。我们可以通过这一点在运行时避开 Java 的访问控制检查,达到我们访问非公有成员变量或方法的目的。为能访问我们需要的非公有成员,我们还需要使用 Java 反射技术。Java 反射是一种强大的工具,它使我们可以在运行时装配代码,而无需在对象之间进行源代码链接,从而使代码更具灵活性。在编译时,Java 编译程序保证了私有成员的私有特性,从而一个类的私有方法和私有成员变量不能被其他类静态引用。然而,通过 Java 反射机制使得我们可以在运行时查询以及访问变量和方法。由于反射是动态的,因此编译时的检查就不再起作用了。

  下面的代码演示了如何利用安全性管理器与反射机制访问私有变量。

  清单 3. 利用反射机制访问类的成员变量

  //获得指定变量的值

  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 方法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。这种方法的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否允许这样做。如果未经允许,安全性管理器抛出一个例外。

  除访问私有变量,我们也可以通过这个方法访问私有方法。

  清单 4. 利用反射机制访问类的成员方法

  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[1];

  args[0] = 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 = 0; 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(111);

  d.setDName("v0");

  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;

  }

  }

相关文章:

  • 电源开关IC
  • Java中sleep和wait的区别
  • iOS的一些面试题分析总结(1)
  • sql中的group by
  • java的finally语句
  • 各种编程语言变量的数据类型
  • java解惑你知多少(一)
  • 《Entity Framework 6 Recipes》中文翻译系列 (7) -----第二章 实体数据建模基础之拆分实体到多表以及拆分表到多实体...
  • java解惑你知多少(二)
  • lnmp的使用
  • java解惑你知多少(三)
  • linux挂载windows共享文件夹
  • java解惑你知多少(四)
  • Android selector的使用
  • java解惑你知多少(五)
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • Android交互
  • ES6 学习笔记(一)let,const和解构赋值
  • flutter的key在widget list的作用以及必要性
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • Mysql数据库的条件查询语句
  • spring + angular 实现导出excel
  • spring-boot List转Page
  • vue:响应原理
  • vue-router的history模式发布配置
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 给第三方使用接口的 URL 签名实现
  • 将回调地狱按在地上摩擦的Promise
  • 理清楚Vue的结构
  • 聊聊flink的BlobWriter
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 算法之不定期更新(一)(2018-04-12)
  • 网页视频流m3u8/ts视频下载
  • 微信公众号开发小记——5.python微信红包
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • !!java web学习笔记(一到五)
  • #pragma once
  • (003)SlickEdit Unity的补全
  • (1) caustics\
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (三)docker:Dockerfile构建容器运行jar包
  • (算法)前K大的和
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • *Django中的Ajax 纯js的书写样式1
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .Net FrameWork总结
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .NET开发不可不知、不可不用的辅助类(一)
  • @CacheInvalidate(name = “xxx“, key = “#results.![a+b]“,multi = true)是什么意思
  • @html.ActionLink的几种参数格式