java梳理-反射

本文属于面试题梳理系列:问题:java反射类的访问私有方法与普通方法相比,需要多处理什么? 
之前梳理类加载的时候,介绍到 初始化的时机之一:用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。下面梳理相关知识。 目录如下:
1.什么是JAVA的反射机制
2.JA VA反射机制API及功能
获取类的Class对象
获取类的Fields
获取类的Method
获取类的Constructor
新建类的实例
       Class<T>的函数newInstance
       通过Constructor对象的方法newInstance
3调用类的函数
         调用private函数
4设置/获取类的属性值
         private属性
下面段落展开详述。
什么是JAVA的反射机制

Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。

Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。

这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

java反射API及功能
在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于 java.lang.reflect 包中

  Class类:代表一个类,位于java.lang包下。

  Field类:代表类的成员变量(成员变量也称为类的属性)。

  Method类:代表类的方法。

  Constructor类:代表类的构造方法。

  Array类:提供了动态创建数组,以及访问数组的元素的静态方法。

获取类的Class对象:

要想使用反射,首先需要获得待操作的类所对应的Class对象。

  Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。

  这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。

常用的获取Class对象的3种方式:

  1).使用Class类的静态方法。例如:  Class.forName("java.lang.String");

 	2).使用类的.class语法。如: 	String.class;
    3).使用对象的getClass()方法。如:
	String str = "aa";
	Class<?> classType1 = str.getClass();
java梳理-反射_第1张图片

获取类的Fields

可以通过反射机制得到某个类的某个属性,然后改变对应于这个类的某个实例的该属性值。JAVA 的Class<T>类提供了几个方法获取类的属性。

public FieldgetField(String name) 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段
public Field[] getFields() 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段
public FieldgetDeclaredField(Stringname) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段
public Field[] getDeclaredFields()

返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段

public static void main(String[] args) throws ClassNotFoundException {
// TODO Auto-generated method stub

Class<?> pc3 = Class.forName("reflect.Person");
Field[] fields=pc3.getFields();
for (Field f : fields)
{
System.out.println("field:"+f);
}
Field[] fields2= pc3.getDeclaredFields();
for (Field f : fields2)
{
System.out.println("d field:"+f);
}
}

运行结果:
field:public java.lang.String reflect.Person.name
d field:private java.lang.String reflect.Person.password
d field:public java.lang.String reflect.Person.name
 可见getFields和getDeclaredFields区别:

getFields返回的是申明为public的属性,包括父类中定义,

getDeclaredFields返回的是指定类定义的所有定义的属性,不包括父类的。

获取类的Method

通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的该方法

Class<T>类提供了几个方法获取类的方法。

public MethodgetMethod(String name,Class<?>... parameterTypes)

返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法

public Method[] getMethods()

返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法

public MethodgetDeclaredMethod(Stringname,Class<?>... parameterTypes)

返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法

public Method[] getDeclaredMethods()

返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法

 根据输出,注意getMethods与getDeclaredMethods的区别。

获取类的Constructor

通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例 

Class<T>类提供了几个方法获取类的构造器。

public Constructor<T> getConstructor(Class<?>... parameterTypes)

返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法

public Constructor<?>[] getConstructors()

返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法

public Constructor<?>[] getDeclaredConstructors()

返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。它们是公共、保护、默认(包)访问和私有构造方法

Constructor<?>[] cons=pc3.getConstructors();
for (int i = 0; i < cons.length; i++) {
System.out.println("cons: "+cons[i]);
}

输出:
cons:  public reflect.Person()
cons:  public reflect.Person(java.lang.String)
 生成对象:
1.先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:
2.先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成
3.带参
  3.调用方法:
通过反射获取类Method对象,调用Field的Invoke方法调用函数。
通过输出,可以看出setName的方法可以调用,但是私有方法调用失败。也就是本文最开始的问题。
4 设置/获取类的属性值

通过反射获取类的Field对象,调用Field方法设置或获取值


 跟上面类似,访问私有属性失败。
一问题解答:访问私有方法,需要调用Class.getDeclaredMethod(String name,Class[] parameterTypes)或者Class.getDeclaredMethods()方法。方法Class.getMethod(String name,Class[] parameterTypes)和Class.getMethods()仅仅返回公有方法。此外还需要设置访问权限。如下:
Method method2 = pc3.getDeclaredMethod("setPri",String.class);
method2.setAccessible(true);
method2.invoke(pp, "test");
为什么呢?
二需要结合method的invoke方法源码去看下。
 2.1.先检查 AccessibleObject的override属性是否为true。
AccessibleObject是Method,Field,Constructor的父类,override属性默认为false,可调用setAccessible方法改变,如果设置为true,则表示可以忽略访问权限的限制,直接调用。
2.2.如果不是ture,则要进行访问权限检测。用Reflection的quickCheckMemberAccess方法先检查是不是public的,如果不是再用Reflection.getCallerClass(1)方法获得到调用这个方法的Class,然后做是否有权限访问的校验,校验之后缓存一次,以便下次如果还是这个类来调用就不用去做校验了,直接用上次的结果,(很奇怪用这种方式缓存,因为这种方式如果下次换个类来调用的话,就不用会缓存了,而再验证一遍,把这次的结果做为缓存,但上一次的缓存结果就被冲掉了。这是一个很简单的缓冲机制,只适用于一个类的重复调用)。 
2.3.调用MethodAccessor的invoke方法。每个Method对象包含一个root对象,root对象里持有一个MethodAccessor对象。我们获得的Method独享相当于一个root对象的镜像,所有这类Method共享root里的MethodAccessor对象,(这个对象由ReflectionFactory方法生成,ReflectionFactory对象在Method类中是static final的由native方法实例化)。
深入分析MethodAccessor的创建机制
  可以看到Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。 
每个实际的Java方法只有一个对应的Method对象作为root,这个root是不会暴露给用户的,而是每次在通过反射获取Method对象时新创建Method对象把root包装起来再给用户。在第一次调用一个实际Java方法对应得Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()真正完成反射调用。 
      那么MethodAccessor是啥呢? jdk源码没有了,从openjdk看下源码。
java梳理-反射_第2张图片
可以看到它只是一个单方法接口,其invoke()方法与Method.invoke()的对应。 
再沿着上面的分析,看看 MethodAccessor是怎么创建的。 代码片段分析:
java梳理-反射_第3张图片
 判断为空 要先reflectionFactory创建,
java梳理-反射_第4张图片
如果noInflation的属性为true则直接返回MethodAccessorGenerator创建的一个MethodAccessor,否则返回DelegatingMethodAccessorImpl,并将他与一个NativeMethodAccessorImpl互相引用。
实际的MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。 为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。 下面对应源码:
   java梳理-反射_第5张图片

每次NativeMethodAccessorImpl.invoke()方法被调用时,都会增加一个调用次数计数器,看超过阈值没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。注意到关键的invoke0()方法是个native方法。它在HotSpot VM里是由JVM_InvokeMethod()函数所支持的,扯个额外话:想想大多数java有关的东西,如果你愿意去看源码分析的话,总会找到jvm这层,当然还有大神继续研究Hotspot的里面的C源码,我觉得主要知识点到这一层就算是比较深入了 。

再看DelegatingMethodAccessorImpl:

java梳理-反射_第6张图片

 这是一个间接层,方便在native与Java版的MethodAccessor之间实现切换。 

native结束,再看java版本:MethodAccessorGenerator。它的基本工作就是在内存里生成新的专用Java类,并将其加载。

 private static synchronized String generateName(boolean isConstructor,
boolean forSerialization)
{
if (isConstructor) {
if (forSerialization) {
int num = ++serializationConstructorSymnum;
return "sun/reflect/GeneratedSerializationConstructorAccessor" + num;
} else {
int num = ++constructorSymnum;
return "sun/reflect/GeneratedConstructorAccessor" + num;
}
} else {
int num = ++methodSymnum;
return "sun/reflect/GeneratedMethodAccessor" + num;
}
}

上面贴出来的方法就是产生名字的。至于大神RednaxelaFX贴出来解释GeneratedMethodAccessor1 这段看不懂。如有大神理解,还请多多留言指正。http://rednaxelafx.iteye.com/blog/548536

 
 ****************总结********************
JAVA反射原理分析及 JAVA反射的应用待整理。

参考:
http://blog.csdn.net/yongjian1092/article/details/7364451
http://www.cnblogs.com/mengdd/archive/2013/01/26/2878136.html
http://www.cnblogs.com/onlywujun/p/3519037.html
http://rednaxelafx.iteye.com/blog/548536

你可能感兴趣的:(java梳理-反射)