写点什么

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

发布于: 2020 年 06 月 04 日

前言

准备过年看下 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**********");
//defaultMethod getColorMethod=appleClass.getDeclaredMethod("getColor");System.out.println("getColorMethod:"+getColorMethod.toString());
//publicMethod getSizeMethod=appleClass.getDeclaredMethod("getSize");System.out.println("getSizeMethod:"+getSizeMethod.toString());
//privateMethod getPriceMethod=appleClass.getDeclaredMethod("getPrice");System.out.println("getPriceMethod:"+getPriceMethod.toString());
//父类的publicMethod 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);
//获取priceMethod 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);
//重新设置priceMethod method = appleClass.getDeclaredMethod("setPrice", int.class);System.out.println("isAccessible:" + method.isAccessible());method.setAccessible(true);method.invoke(apple, 100);
//再次获取priceotherMethod = 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 

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


发布于: 2020 年 06 月 04 日阅读数: 1758
用户头像

学习Java的小姐姐 号主 & 工作3年的程序猿 2020.06.04 加入

还未添加个人简介

评论 (3 条评论)

发布
用户头像
干货满满
2020 年 06 月 05 日 18:36
回复
用户头像
辛苦小姐姐码了这么多字,InfoQ首页推荐。
2020 年 06 月 05 日 09:03
回复
多谢
2020 年 06 月 05 日 17:00
回复
没有更多了
万字总结——反射(框架之魂)