首页 > 代码库 > java梳理-反射

java梳理-反射

本文属于面试题梳理系列:问题:java反射类的訪问私有方法与普通方法相比,须要多处理什么? 
之前梳理类载入的时候,介绍到初始化的时机之中的一个:用java.lang.reflect包的方法对类进行反射调用的时候,假设类没有进行过初始化。则须要先触发其初始化。以下梳理相关知识。文件夹例如以下:
1.什么是JAVA的反射机制
2.JAVA反射机制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();

技术分享

获取类的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,然后做是否有权限訪问的校验,校验之后缓存一次。以便下次假设还是这个类来调用就不用去做校验了。直接用上次的结果,(非常奇怪用这样的方式缓存,由于这样的方式假设下次换个类来调用的话,就不用会缓存了,而再验证一遍,把这次的结果做为缓存。但上一次的缓存结果就被冲掉了。这是一个非常easy的缓冲机制,仅仅适用于一个类的反复调用)。 
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看下源代码。
技术分享
能够看到它仅仅是一个单方法接口,其invoke()方法与Method.invoke()的相应。 
再沿着上面的分析。看看MethodAccessor是怎么创建的。代码片段分析:
技术分享
 推断为空 要先reflectionFactory创建,
技术分享
假设noInflation的属性为true则直接返回MethodAccessorGenerator创建的一个MethodAccessor。否则返回DelegatingMethodAccessorImpl。并将他与一个NativeMethodAccessorImpl互相引用。
实际的MethodAccessor实现有两个版本号,一个是Java实现的,还有一个是native code实现的。Java实现的版本号在初始化时须要较多时间,但长久来说性能较好;native版本号正好相反。启动时相对较快。但执行时间长了之后速度就比只是Java版了。这是HotSpot的优化方式带来的性能特性,同一时候也是很多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联。于是执行时间长了之后反而是托管版本号的代码更快些。

 为了权衡两个版本号的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时。开头若干次使用native版。等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类。生成当中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。

以下相应源代码:
  技术分享

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

再看DelegatingMethodAccessorImpl:

技术分享

 这是一个间接层,方便在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梳理-反射