万字总结——反射(框架之魂)

目录

前言

反射的概述(基础部分开始)

为什么要反射?

反射的用途

获取Class文件对象的三种方式

反射的使用

1.通过反射获取所有参数 getDeclaredFields

2.通过反射获取指定参数getDeclaredField

3.通过反射获取所有pubic类型的参数 getFields

4.通过反射获取指定public类型的参数 getField

插曲:为什么getFields和getField只能获取public类型的字段?

5.通过反射获取所有方法 getDeclaredMethods

6.通过反射获取指定方法 getDeclaredMethod

7.通过反射获取所有public类型的方法 getMethods

8.通过反射获取指定public类型的方法 getMethod

9.通过反射获取所有构造方法 getDeclaredConstuctors

10.通过反射获取某个带参数的构造方法 getDeclaredConstructor

11.通过反射获取所有public类型的构造方法getConstructors

12.通过反射获取某个public类型的构造方法getConstructor

13.通过无参构造来获取该类对象 newInstance()

14.通过有参构造来获取该类对象 newInstance(XXXX)

15.获取类名包含包路径 getName()

16.获取类名不包含包路径 getSimpleName()

17.通过反射调用方法 invoke

18.判断方法是否能调用isAccessible

19.设置安全检查开关setAccessible

常见面试题解答(进阶部分开始)

被反射的类是否一定需要无参构造方法?

反射的使用有什么优势和劣势?

为什么说反射可以降低耦合?

反射比较损耗性能,为什么这样说?(重点)

反射中的setAccessible()方法是否破坏了类的访问规则

反射源码解析

MethodAccessor的C语言实现(默认)

MethodAccessor的Java实现

例子

结语

参考资料


前言

准备过年看下Spring源码,用来唬人,哈哈哈哈。正经点,是为了在遇到问题的时候,能知其然而知其所以然。但是在开始前,先恶补下基础知识。今天看框架之魂——反射。

反射的概述(基础部分开始)

反射是在编译状态,对某个类一无所知 ,但在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法。

这个说太干涩了,没有灵魂,就像下面两张图。

所以咱来举个例子,拒绝没有灵魂。O(∩_∩)O哈哈~

为什么要反射?

如果我们没有Orange类,那该类在编译的时候就会报错找不到该类。这是我们平常使用的“正射”。这个名字是为了和反射相对应,不是官方的术语。

但是这存在着一个明显的缺点,就是在main方法里调用的是Apple类,并没有调用Orange类,所以应该是可以正常调用的,当我想要调用Orange类的时候,再报错即可。但是,事与愿违,事情不是照着我们的想法而发展的。

我们需要一种在编译时不检查类的调用情况,只有在运行时,才根据相应的要求调用相应的类,这就是“反射”。

反射的用途

反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。

举一个例子,在运用 Struts 2 框架的开发中我们一般会在 struts.xml 里去配置 Action,比如:

<action name="login"       
        class="org.ScZyhSoft.test.action.SimpleLoginAction"   
        method="execute">      
      <result>/shop/shop-index.jsp</result>     
      <result name="error">login.jsp</result>
</action> 

配置文件与 Action 建立了一种映射关系,当 View 层发出请求时,请求会被 StrutsPrepareAndExecuteFilter 拦截,然后 StrutsPrepareAndExecuteFilter 会去动态地创建 Action 实例。比如我们请求 login.action,那么 StrutsPrepareAndExecuteFilter就会去解析struts.xml文件,检索action中name为login的Action,并根据class属性创建SimpleLoginAction实例,并用invoke方法来调用execute方法,这个过程离不开反射。

获取Class文件对象的三种方式

万事万物都是对象。

Apple apple=new Apple();中的apple为Apple的一个实例。那Apple对象是哪个的实例呢?

其实是Class类的实例。

我们可以看他的注释,私有的构造方法,只有JVM才能创建对象。

如果我们能找到某个对象的Class类,即可以创建其实例。

  • 静态属性class

  • Object类的getClass方法,如果知道实例,直接调用其getClass方法。

  • Class类的静态方法forName(),参数为类的完整路径(推荐使用)

这里需要注意,通过类的全路径名获取Class对象会抛出一个异常,要用try....catch...捕获异常。如果根据类路径找不到这个类那么就会抛出这个异常,Class类中forName方法源码如下:

注:虽然写了三种方式,但平常使用最多,最推荐的是第三种方式,因为第一种方式需要知道类,第二种方式需要知道实例,如果知道了这些,可以直接调用其方法和参数,没必要再用Class来实现功能。举个例子,你从北京去上海,第一种方式直达就行,第二种方式和第三种方式则是先从北京到云南,再从云南到上海,显得太冗余。

如果要加载的类是内部类,要记得加$。

反射的使用

我们以Apple类为例,利用发射来获取其参数和方法。其有三个参数,默认default参数color,公有public参数size,私有private参数price。三个构造方法,分别是默认default构造,公有public带有三个参数的有参构造,私有带有两个参数的有参构造。六个setter/getter方法公有方法,分别是color的默认default隔离级别的setter/getter方法,size的public隔离级别的setter/getter方法,price的private隔离级别的setter/getter方法。toString和三个参数的setter/getter方法。最后还有一个public隔离级别的toString方法。这样详细展开的描述,看起来很复杂,其实很简单的,具体代码如下:

package com.eastrobot.reflect;

public class Apple extends Fruit{
    String color;//默认default
    public int size;
    private int price;

    Apple() {//默认default
        System.out.println("Apple的无参构造");
    }

    public Apple(String color, int size, int price) {
        this.color = color;
        this.size = size;
        this.price = price;
        System.out.println("Apple的有参构造——三个参数");
    }

    private Apple(String color, int size) {
        this.color = color;
        this.size = size;
        this.price = 10;
        System.out.println("Apple的有参构造——两个参数");
    }

    @Override
    public String toString() {
        return "color:" + color + ",size:" + size + ",price:" + price;
    }

    //默认default
    String getColor() {
        return color;
    }

    public int getSize() {
        return size;
    }

    private int getPrice() {
        return price;
    }

    //默认default
    void setColor(String color) {
        this.color = color;
    }

    public void setSize(int size) {
        this.size = size;
    }

    private void setPrice(int price) {
        this.price = price;
    }
}

继承的父类Fruit,包括一个public类型的参数taste,和其public类型的setter/getter方法。

public class Fruit {   
 public String taste; 
 public String getTaste() {  
      return taste;    
 }   
 public void setTaste(String taste) {   
     this.taste = taste;  
  }
}

1.通过反射获取所有参数 getDeclaredFields

System.out.println("getDeclaredFields**********");
Field[] fields=appleClass.getDeclaredFields();
for(Field field:fields){   
 System.out.println(field.toString());
}

运行结果如下:

 

注:不管何种隔离级别,getDeclaredFields都会获取到所有参数。

2.通过反射获取指定参数getDeclaredField

//指定参数
System.out.println("getDeclaredField**********");
Field colorField=appleClass.getDeclaredField("color");
System.out.println("color:"+colorField.toString());

Field sizeField=appleClass.getDeclaredField("size");
System.out.println("size:"+sizeField.toString());

Field priceField=appleClass.getDeclaredField("price");
System.out.println("price:"+priceField.toString());

运行结果如下:

 

 

 

 

注:不管何种隔离级别,getDeclaredField可以通过输入值获取指定参数。

3.通过反射获取所有pubic类型的参数 getFields

System.out.println("getFields**********");
Field[] fields=appleClass.getFields();
for(Field field:fields){    
    System.out.println(field.toString());
}

运行结果如下:

 

注:只能通过反射获取public类型的属性,也包括继承自父类的属性。

4.通过反射获取指定public类型的参数 getField

Field colorField=appleClass.getField("color");
System.out.println("color:"+colorField.toString());

运行结果如下:

-------------------手动分割线-------------------

Field sizeField=appleClass.getField("size");
System.out.println("size:"+sizeField.toString());

运行结果如下:

-------------------手动分割线-------------------

Field priceField=appleClass.getField("price");
System.out.println("price:"+priceField.toString());

运行结果如下:

 

注:只有public类型才能通过getField方法获取到,其他类型均获取不到。

看到这里,有些小伙伴要问了,这是为啥,理由呢?咱不能死记硬背,这样过两天就忘了,记得不牢固,咱来瞅瞅底层干了啥。

插曲:为什么getFields和getField只能获取public类型的字段?

我们以getField为例,观察getDeclaredField和getField的区别,可以看到两者都调用了privateGetDeclaredFields方法,但是区别是getDeclaredField方法中的参数publicOnly是false,getField方法中的参数publicOnly为true。

getDeclaredField方法:

getField方法:

那privateGetDeclaredFields里面干了啥,我们看下。

我们可以看到如果为true,就取declaredPublicFields字段,即public字段,如果为false,就取DeclaredFields。

5.通过反射获取所有方法 getDeclaredMethods

//所有方法
System.out.println("getDeclaredMethods**********");
Method[] methods=appleClass.getDeclaredMethods();
for(Method method:methods){    
    System.out.println(method.toString());
}

运行结果如下:

6.通过反射获取指定方法 getDeclaredMethod

//指定方法
System.out.println("getDeclaredMethod**********");

//default
Method getColorMethod=appleClass.getDeclaredMethod("getColor");
System.out.println("getColorMethod:"+getColorMethod.toString());

//public
Method getSizeMethod=appleClass.getDeclaredMethod("getSize");
System.out.println("getSizeMethod:"+getSizeMethod.toString());

//private
Method getPriceMethod=appleClass.getDeclaredMethod("getPrice");
System.out.println("getPriceMethod:"+getPriceMethod.toString());

//父类的public
Method getTasteMethod=appleClass.getDeclaredMethod("getTaste");
System.out.println("getTasteMethod:"+getTasteMethod.toString());

运行结果如下:

注:getDeclaredMethod只能获取自己定义的方法,不能获取从父类的方法。

7.通过反射获取所有public类型的方法 getMethods

//所有方法
System.out.println("getMethods**********");
Method[] methods=appleClass.getMethods();
for(Method method:methods){
    System.out.println(method.toString());
}

运行结果如下:

注:getMethods可以通过反射获取所有的public方法,包括父类的public方法。

8.通过反射获取指定public类型的方法 getMethod

//指定方法
System.out.println("getMethod**********");
Method method=appleClass.getMethod("toString");
System.out.println(method.toString());

运行结果如下:

9.通过反射获取所有构造方法 getDeclaredConstuctors

//构造方法
System.out.println("getDeclaredConstructors**********");
Constructor[] constructors=appleClass.getDeclaredConstructors();
for(Constructor constructor:constructors){   
 System.out.println(constructor.toString());
}

运行结果如下:

10.通过反射获取某个带参数的构造方法 getDeclaredConstructor

//指定构造方法
System.out.println("getDeclaredConstructor**********");
Class[] cArg = new Class[3];
cArg[0] = String.class;
cArg[1] = int.class;
cArg[2] = int.class;
Constructor constructor=appleClass.getDeclaredConstructor(cArg);
System.out.println(constructor.toString());

运行结果如下:

11.通过反射获取所有public类型的构造方法getConstructors

System.out.println("getConstructors**********");
Constructor[] constructors=appleClass.getConstructors();
for(Constructor constructor:constructors){
    System.out.println(constructor.toString());
}

运行结果:

12.通过反射获取某个public类型的构造方法getConstructor

//构造方法
System.out.println("getConstructor**********");
Constructor constructor1 = appleClass.getConstructor(String.class,int.class,int.class);
System.out.println("public类型的有参构造:" + constructor1.toString());

Constructor constructor2 = appleClass.getConstructor(String.class, int.class);
System.out.println("private类型的有参构造:" + constructor2.toString());

运行结果:

13.通过无参构造来获取该类对象 newInstance()

//调用无参构造创建对象
Class appleClass = Class.forName("com.eastrobot.reflect.Apple");
Apple apple=(Apple)appleClass.newInstance();

运行结果如下:

14.通过有参构造来获取该类对象 newInstance(XXXX)

Constructor constructor=appleClass.getConstructor(String.class,int.class,int.class);
Apple apple=(Apple)constructor.newInstance("红色",10,5);

运行结果如下:

15.获取类名包含包路径 getName()

String name= appleClass.getName();
System.out.println("name:"+name);

运行结果如下:

16.获取类名不包含包路径 getSimpleName()

String simpleName =appleClass.getSimpleName();
System.out.println("simpleName:"+simpleName);

运行结果如下:

17.通过反射调用方法 invoke

//调用无参构造创建对象
Class appleClass = Class.forName("com.eastrobot.reflect.Apple");

//调用有参构造
Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class);
Apple apple = (Apple) constructor.newInstance("红色", 10, 20);

//获取toString方法并调用
Method method = appleClass.getDeclaredMethod("toString");
String str=(String)method.invoke(apple);
System.out.println(str);

运行结果:

注:invoke+实例可以调用相关public方法。

18.判断方法是否能调用isAccessible

//调用无参构造创建对象
Class appleClass = Class.forName("com.eastrobot.reflect.Apple");

//调用有参构造
Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class);
Apple apple = (Apple) constructor.newInstance("红色", 10, 20);

//获取public的getSize方法并调用
Method method = appleClass.getDeclaredMethod("getSize");
System.out.println("getSize方法的isAccessible:" + method.isAccessible());
int size = (Integer) method.invoke(apple);
System.out.println("size:" + size);

//获取private的getPrice方法并调用
method = appleClass.getDeclaredMethod("getPrice");
System.out.println("getPrice的isAccessible:" + method.isAccessible());
int price = (Integer) method.invoke(apple);
System.out.println("price:" + price);

运行结果:

注:这样一看,public和private类型的isAccessible都为false,但是public类型的值可以获取到,但是private类型的值并不能获取到。其实isAccessible()值为 true或false,是指启用和禁用访问安全检查的开关,如果为true,则取消安全检查,为false,则执行安全检查。如上,两者都为false,说明两者的进行了安全检查,getSize为public类型,则可以获取值,而getPrice为private,则不能获取值。

19.设置安全检查开关setAccessible

//调用无参构造创建对象
Class appleClass = Class.forName("com.eastrobot.reflect.Apple");

//调用有参构造
Constructor constructor = appleClass.getDeclaredConstructor(String.class, int.class, int.class);
Apple apple = (Apple) constructor.newInstance("红色", 10, 20);

//获取price
Method otherMethod = appleClass.getDeclaredMethod("getPrice");
System.out.println("getPrice方法的isAccessible:" + otherMethod.isAccessible());
otherMethod.setAccessible(true);
int price = (Integer) otherMethod.invoke(apple);
System.out.println("之前的price:" + price);

//重新设置price
Method method = appleClass.getDeclaredMethod("setPrice", int.class);
System.out.println("isAccessible:" + method.isAccessible());
method.setAccessible(true);
method.invoke(apple, 100);

//再次获取price
otherMethod = appleClass.getDeclaredMethod("getPrice");
otherMethod.setAccessible(true);
price = (Integer) otherMethod.invoke(apple);
System.out.println("之后的price:" + price);

运行结果:

注:setAccessible(true)表示取消安全检查,setAccessible(false)表示启用安全检查。

常见面试题解答(进阶部分开始)

被反射的类是否一定需要无参构造方法?

不一样。因为有参构造方法也可以反射,具体代码如下:

Constructor constructor=appleClass.getConstructor(String.class,int.class,int.class);
Apple apple=(Apple)constructor.newInstance("红色",10,5);

反射的使用有什么优势和劣势?

优势:

在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息。反射提高了Java程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

劣势:

使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码

使用反射会模糊程序内部逻辑,程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。(这也就是看源码为什么这么难?哎。。。。)

为什么说反射可以降低耦合?

因为反射不是硬编码,在运行时可以灵活发现该类的详细信息,降低了代码之间的耦合性。

反射比较损耗性能,为什么这样说?(重点)

怎么去判断一个函数的性能?因为函数的执行太快太快了,你需要一个放慢镜,这样才能捕捉到他的速度。怎么做?把一个函数执行一百万遍或者一千万遍,你才能真正了解一个函数的性能。也就是,你如果想判断性能,你就不能还停留在秒级,毫秒级的概念。

如下是将直接获取实例,直接获取方法,反射获取实例,反射获取方法分别执行1百万次所花费差。

try {    
    //直接获取实例
    long startTime1 = System.currentTimeMillis();   
    for (int i = 0; i < 1000000; i++) { 
       new Apple();   
    }  
    long endTime1 = System.currentTimeMillis();  
    System.out.println("直接获取实例时间:" + (endTime1 - startTime1));   
 
    //直接获取方法  
    long startTime2= System.currentTimeMillis();   
    for (int i = 0; i < 1000000; i++) {  
      new Apple().toString();   
    }  
    long endTime2 = System.currentTimeMillis();  
    System.out.println("直接获取方法时间:" + (endTime2- startTime2)); 
   
   //反射获取实例  
   Class appleClass=Class.forName("com.eastrobot.reflect.Apple");   
   long startTime3 = System.currentTimeMillis();   
   for (int i = 0; i < 1000000; i++) {
       appleClass.getDeclaredConstructor().newInstance();  
    }   
   long endTime3 = System.currentTimeMillis(); 
   System.out.println("反射获取实例:" + (endTime3 - startTime3));   
 
   //反射获取方法  
   Apple apple= (Apple)appleClass.getDeclaredConstructor().newInstance();   
   long startTime4 = System.currentTimeMillis();   
   for (int i = 0; i < 1000000; i++) {    
     Method method=appleClass.getMethod("toString");    
     method.invoke(apple);   
   }    
   long endTime4 = System.currentTimeMillis();  
   System.out.println("反射获取方法:" + (endTime4 - startTime4));

运行结果截图:

我们可以看到反射的确会导致性能问题,但反射导致的性能问题是否严重跟使用的次数有关系,如果控制在100次以内,基本上没什么差别,如果调用次数超过了100次,性能差异会很明显。

打个比方,如果快递员就在你住的小区,那么你报一个地址:xx栋xx号,那么快递员就可以马上知道你在哪里,直接就去到你家门口;但是,如果快递员是第一次来你们这里,他是不是首先得查查百度地图,看看怎么开车过去,然后到了小区是不是得先问问物管xx栋怎么找,然后,有可能转在楼下转了两个圈才到了你的门前。

我们看上面这个场景,如果快递员不熟悉你的小区,是不是会慢点,他的时间主要花费在了查找百度地图,询问物业管理。OK,反射也是一样,因为我事先什么都不知道,所以我得花时间查询一些其他资料,然后我才能找到你。

综上,大部分我们使用反射是不考虑性能的,平常使用的次数较少,如果真的遇到性能问题,如反射的效率影响到程序逻辑,可以采用缓存或Java字节码增强技术,参照库有asm,也有第三方工具库reflectAsm(https://github.com/EsotericSoftware/reflectasm)。

反射中的setAccessible()方法是否破坏了类的访问规则

setAccessible(true)取消了Java的权限控制检查(注意不是改变方法或字段的访问权限),对于setAccessible()方法是否会破坏类的访问规则,产生安全隐患,见下:

反射源码解析

我们跟进Method的invoke方法,分为两步,一是语言访问级别是否为重写,如果不是重写则调用Reflection的quickCheckMemberAccess方法,即通过Modifiers 判断是否具有访问权限,quickCheckMemberAccess方法主要是简单地判断 modifiers 是不是 public,如果不是的话就返回 false。所以 protected、private、default 修饰符都会返回 false,只有 public 都会返回 true。如果为false,则调用checkAccess方法。二是获取MethodAccessor对象,并调用其invoke方法。

public final class Method extends AccessibleObject implements GenericDeclaration, Member {   
     private volatile MethodAccessor methodAccessor; 
    //每个Java方法只有一个对应Method对象作为root,这个root不会暴露给用户,
    //而是每次通过反射获取Method对象时新创建的Method对象将root包装起来。
     private Method   root;

    @CallerSensitive 
    public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;
        //在第一次调用一个实际Java方法应该的Method对象的invoke方法之前
        //实现调用逻辑的MethodAccessor对象还没有创建
        //等到第一次调用时才创建MethodAccessor,并通过该MethodAccessor.invoke真正完成反射调用
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        //invoke并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理
        return ma.invoke(obj, args);
    } 

    ...

     private MethodAccessor acquireMethodAccessor() {
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            //调用ReflectionFactory的newMethodAccessor方法,见下
            tmp = reflectionFactory.newMethodAccessor(this);
            //更新root,以便下次直接使用
            setMethodAccessor(tmp);
        }
        return tmp;
    }

    ...

     void setMethodAccessor(MethodAccessor accessor) {
        methodAccessor = accessor;
        // Propagate up
        if (root != null) {
            root.setMethodAccessor(accessor);
        }
    }

Reflection类:

public static boolean quickCheckMemberAccess(Class<?> memberClass,
                                                 int modifiers)
{
    return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers);
}

ReflectionFactory类:

private static boolean noInflation = false;
//选择java版还是C语言版的阈值
private static int inflationThreshold = 15;

public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();
        if (noInflation) {
            //java版
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            //c语言版
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    }

如上述代码所示,实际的MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。

为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。
Sun的JDK是从1.4系开始采用这种优化的,主要作者是Ken Russell

MethodAccessor的C语言实现(默认)

C语言版的MethodAccessor主要涉及这NativeMethodAccessorImpl和DelegatingMethodAccessorImpl两个类,而DelegatingMethodAccessorImpl是间接层,不是太重要,就不贴代码啦。以下是NativeMethodAccessorImpl的代码,核心是invoke方法:

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if (++this.numInvocations > ReflectionFactory.inflationThreshold()) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

        return invoke0(this.method, var1, var2);
    }

    void setParent(DelegatingMethodAccessorImpl var1) {
        this.parent = var1;
    }

    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}

每次NativeMethodAccessorImpl.invoke()方法被调用时,都会增加一个调用次数计数器,看超过阈值没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。

MethodAccessor的Java实现

return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());

Java的MethodAccessor主要涉及的是MethodAccessorGenerator类,具体代码超长,只截取了部分代码,主要有三个方法,直接就是上述的generateMethod方法,代码如下:

public MethodAccessor generateMethod(Class var1, String var2, Class[] var3, Class var4, Class[] var5, int var6) {
        return (MethodAccessor)this.generate(var1, var2, var3, var4, var5, var6, false, false, (Class)null);
    }
 private MagicAccessorImpl generate(final Class var1, String var2, Class[] var3, Class var4, Class[] var5, int var6, boolean var7, boolean var8, Class var9) {
        ByteVector var10 = ByteVectorFactory.create();
        this.asm = new ClassFileAssembler(var10);
        this.declaringClass = var1;
        this.parameterTypes = var3;
        this.returnType = var4;
        this.modifiers = var6;
        this.isConstructor = var7;
        this.forSerialization = var8;
        this.asm.emitMagicAndVersion();
        short var11 = 42;
        boolean var12 = this.usesPrimitiveTypes();
        if (var12) {
            var11 = (short)(var11 + 72);
        }

        if (var8) {
            var11 = (short)(var11 + 2);
        }

        var11 += (short)(2 * this.numNonPrimitiveParameterTypes());
        this.asm.emitShort(add(var11, (short)1));
        final String var13 = generateName(var7, var8);
        this.asm.emitConstantPoolUTF8(var13);
        this.asm.emitConstantPoolClass(this.asm.cpi());
        this.thisClass = this.asm.cpi();
        if (var7) {
            if (var8) {
                this.asm.emitConstantPoolUTF8("sun/reflect/SerializationConstructorAccessorImpl");
            } else {
                this.asm.emitConstantPoolUTF8("sun/reflect/ConstructorAccessorImpl");
            }
        } else {
            this.asm.emitConstantPoolUTF8("sun/reflect/MethodAccessorImpl");
        }

        this.asm.emitConstantPoolClass(this.asm.cpi());
        this.superClass = this.asm.cpi();
        this.asm.emitConstantPoolUTF8(getClassName(var1, false));
        this.asm.emitConstantPoolClass(this.asm.cpi());
        this.targetClass = this.asm.cpi();
        short var14 = 0;
        if (var8) {
            this.asm.emitConstantPoolUTF8(getClassName(var9, false));
            this.asm.emitConstantPoolClass(this.asm.cpi());
            var14 = this.asm.cpi();
        }

        this.asm.emitConstantPoolUTF8(var2);
        this.asm.emitConstantPoolUTF8(this.buildInternalSignature());
        this.asm.emitConstantPoolNameAndType(sub(this.asm.cpi(), (short)1), this.asm.cpi());
        if (this.isInterface()) {
            this.asm.emitConstantPoolInterfaceMethodref(this.targetClass, this.asm.cpi());
        } else if (var8) {
            this.asm.emitConstantPoolMethodref(var14, this.asm.cpi());
        } else {
            this.asm.emitConstantPoolMethodref(this.targetClass, this.asm.cpi());
        }

        this.targetMethodRef = this.asm.cpi();
        if (var7) {
            this.asm.emitConstantPoolUTF8("newInstance");
        } else {
            this.asm.emitConstantPoolUTF8("invoke");
        }

        this.invokeIdx = this.asm.cpi();
        if (var7) {
            this.asm.emitConstantPoolUTF8("([Ljava/lang/Object;)Ljava/lang/Object;");
        } else {
            this.asm.emitConstantPoolUTF8("(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
        }

        this.invokeDescriptorIdx = this.asm.cpi();
        this.nonPrimitiveParametersBaseIdx = add(this.asm.cpi(), (short)2);

        for(int var15 = 0; var15 < var3.length; ++var15) {
            Class var16 = var3[var15];
            if (!isPrimitive(var16)) {
                this.asm.emitConstantPoolUTF8(getClassName(var16, false));
                this.asm.emitConstantPoolClass(this.asm.cpi());
            }
        }

        this.emitCommonConstantPoolEntries();
        if (var12) {
            this.emitBoxingContantPoolEntries();
        }

        if (this.asm.cpi() != var11) {
            throw new InternalError("Adjust this code (cpi = " + this.asm.cpi() + ", numCPEntries = " + var11 + ")");
        } else {
            this.asm.emitShort((short)1);
            this.asm.emitShort(this.thisClass);
            this.asm.emitShort(this.superClass);
            this.asm.emitShort((short)0);
            this.asm.emitShort((short)0);
            this.asm.emitShort((short)2);
            this.emitConstructor();
            this.emitInvoke();
            this.asm.emitShort((short)0);
            var10.trim();
            final byte[] var17 = var10.getData();
            return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() {
                public MagicAccessorImpl run() {
                    try {
                        return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();
                    } catch (InstantiationException var2) {
                        throw (InternalError)(new InternalError()).initCause(var2);
                    } catch (IllegalAccessException var3) {
                        throw (InternalError)(new InternalError()).initCause(var3);
                    }
                }
            });
        }
    } 
private static synchronized String generateName(boolean var0, boolean var1) {
        int var2;
        if (var0) {
            if (var1) {
                var2 = ++serializationConstructorSymnum;
                return "sun/reflect/GeneratedSerializationConstructorAccessor" + var2;
            } else {
                var2 = ++constructorSymnum;
                return "sun/reflect/GeneratedConstructorAccessor" + var2;
            }
        } else {
            var2 = ++methodSymnum;
            return "sun/reflect/GeneratedMethodAccessor" + var2;
        }
    }

去阅读源码的话,可以看到MethodAccessorGenerator是如何一点点把Java版的MethodAccessor实现类生产出来的,其实就是一个逐步解析的过程。

此时要注意的是最后的“sun/reflect/GeneratedMethodAccessor”+var2的代码。

例子

以上空说无用,太干涩,咱来个例子。

public class Foo {

public void foo(String name) {       
     System.out.println("Hello, " + name); 
   }
}
public class test {    
    public static void main(String[] args) {  
          try {          
                  Class<?> clz = Class.forName("com.eastrobot.reflect.Foo");      
                  Object o = clz.newInstance();  
                  Method m = clz.getMethod("foo", String.class);    
                  for (int i = 0; i < 17; i++) {        
                        m.invoke(o, Integer.toString(i));      
                  }       
         } catch (Exception e) {
         } 
   }
}

除了上述代码,还需要在idea配置相关的运行参数,添加-XX:+TraceClassLoading参数,其为要求打印加载类的监控信息。

我们先用上述的例子执行下,运行结果如下,前面十五次是正常的,到第16次的时候,出现了很多打印信息,我已将一行标红,“GeneratedMethodAccessor1”,这其实就是上面说的Java版获取MethodAccessorGenerator的最后一行,1为自增参数。当第17次的时候,就不会用Java版的方式重新获取,而是直接复用啦。

结语

终于结束了,边玩边写,写了五天,累死了,答应我,一定要好好看,好吗?

如有说的不对地方,欢迎指正。

参考资料

Java反射详细介绍

Java反射中getDeclaredField和getField的区别

java反射的使用场合和作用、及其优缺点

反射是否真的会让你的程序性能降低?

深入解析Java反射(1) - 基础

关于反射调用方法的一个log 

反射进阶,编写反射代码值得注意的诸多细节

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章