java反射机制源码分析

前言

我们先对前几天的学习进行总结,前几天我们主要结合源码学习了java中的集合,重点分析了HashMap散列桶的实现,还让大伙儿去看红黑树。今天就来学习java反射相关的东西,反射可是java一个很重要的高级特性,很多框架都是基于反射实现的,提高对反射相关机制的了解也有利于我们将来造轮子。接下来我们结合源码以及java虚拟机来分析反射。

定义

JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。这就意味着我们在对一个类未知的情况下可以通过多种方式获取类,调用方法和属性,这是JDK中动态代理实现的基础,当然还有别的动态代理实现的方式,例如CGLIB等

基础复习

不知道各位看官对反射的基本使用忘记没有,我在这里带大家复习一下。

首先,我们都知道java中的实例对象都是通过对应的Class对象创建的,而Class对象的加载在我的java虚拟机类加载机制中已经有了分析,感兴趣的可以看https://blog.csdn.net/qq_41861259/article/details/103050011。所以如果我们要操作一个对象,首先要获取到他的Class对象,那么获取Class对象的方式有哪些呢?主要有三种方式,如代码所示:
 

//通过class常量获取
Class c = Integer.class;
//通过类的全限定名获取
Class c1 = Class.forName("java.lang.Integer");
//通过实例获取Class对象
Integer integer = new Integer(1);
integer.getClass();

当我们获取到Class对象,那么我们要操作一个实例对象还需要什么呢?那就是方法,我们通过Class对象得到实例,通过方法名获取方法,调用方法,当然我们的构造方法和私有方法也都是可以获取的,需要设置setAccessible(true),还可以通过getSuperclass()获取父类

//得到Class对象之后
//获取方法,指定方法名和参数
 Method method = c.getMethod(”方法名“,"参数“);
//实例化对象
 Object o = c.newInstance();
//调用invoke方法执行方法
 method.invoke(o,"hahah");

正题

先看下反射的整体框架,这是我自己画的,也就这个水平了,哈哈!其实最开始我的想法是通过Method、Filed这两个类成员来展开画正题的框架,并且实际画的过程中两者也都整体对称的感觉,但是当加入一些依赖的时候,渐渐的Reflection就成为了中心,此时真是感概那些大佬的设计啊!!!

我们先来看Class对象的获取,这里我使用Class的forName方法Class c = Class.forName("com.reflect.Person");
我们先补充说明下@CallerSensitive注解,我在网上一个解释:

这个注解是为了堵住漏洞用的。曾经有黑客通过构造双重反射来提升权限,
原理是当时反射只检查固定深度的调用者的类,看它有没有特权,
例如固定看两层的调用者(getCallerClass(2))。如果我的类本来没足够
权限群访问某些信息,那我就可以通过双重反射去达到目的:反射相关
的类是有很高权限的,而在 我->反射1->反射2 这样的调用链上,反射2
检查权限时看到的是反射1的类,这就被欺骗了,导致安全漏洞。
使用CallerSensitive后,getCallerClass不再用固定深度去寻找
actual caller(“我”),而是把所有跟反射相关的接口方法都标注上
CallerSensitive,搜索时凡看到该注解都直接跳过,这样就有效解决了
前面举例的问题

通过native方法forName0()获取到我们需要的Class对象,然后通过这个对象获取方法,例如:Method method = c.getMethod("getUsername");

然后进入我们的getMethod0方法,其中MethodArray这个静态内部类就是维护了一个Method数组

深入到此都没有看到具体获取method方法的实现,但是不要着急,我们继续看,注释我已经写在代码中,总体来说,我们是通过查询ReflectionData而得到的,这个私有的静态内部类中有我们需要的很多信息,如代码后的贴图所示:
 

private Method privateGetMethodRecursive(String name,
            Class<?>[] parameterTypes,
            boolean includeStaticMethods,
            MethodArray allInterfaceCandidates) {
        // Search declared public methods
        //查询公共的方法
        if ((res = searchMethods(privateGetDeclaredMethods(true),
                                 name,
                                 parameterTypes)) != null) {
            if (includeStaticMethods || !Modifier.isStatic(res.getModifiers()))
                return res;
        }
        // Search superclass's methods
        //查询父类中的方法
        if (!isInterface()) {
            Class<? super T> c = getSuperclass();
            if (c != null) {
                //这里产生递归查询
                if ((res = c.getMethod0(name, parameterTypes, true)) != null) {
                    return res;
                }
            }
        }
        //查询父类接口中的方法
        // Search superinterfaces' methods
        Class<?>[] interfaces = getInterfaces();
        for (Class<?> c : interfaces)
            if ((res = c.getMethod0(name, parameterTypes, false)) != null)
                allInterfaceCandidates.add(res);
        // Not found
        return null;
    }


一直到这里我们只是知道了方法的获取,那么方法是如何执行的?大家一定要耐心看,我们在开始查询方法的时候,将方法的Class对象以及方法的信息都记录了下来,下面我们来调用invoke方法method.invoke(c)

分析到这里过后,就主要是我们的MethodAccessor的具体实现了。

invoke:在其内部交给MethodAccessor处理,而这个接口又有两种实现,本地方法来实现反射调用 和委派模式,method实例的第一次反射调用会生成一个委派实现,具体的就是本地实现(进入虚拟机内部以后,便拥有了Methos实例所指向方法的具体地址,然后就可以调用目标方法本地方法来实现反射调用,其中调用的顺序:

  • Method.invoke
  • 委派实现(DelegatingMethodAccessorImpl)
  • 本地方法(NativeMethodAccessorImpl),到达目标方法采用委派做中间层

那么采用委派做中间层,为啥要用委派,不直接调用本地方法?

  • 其实,Java的反射调用机制还设立了另一种动态生成字节码的实现(动态实现),直接使用invoke指令调用目标方法,采用委派方便本地实现和动态实现中切换

  • 动态实现效率比本地方法快几十倍,因为动态不需要java中调用C++,在回到java,但是生成字节码非常耗时,调用一次的话,本地放法快三四倍

  • Inflation :许多反射仅调用一次,所以设置了一个阀值15,达到十五就动态生成字节码,委派模式切换为动态实现,调用GeneratedMethodAccessor1

4. 反射调用的开销

  • Class.forName会调用本地方法,Class.getMethod 会遍历类的所有共有方法,甚至是父类方法

  • getMethod为代表的查找方法会返回结果的Copy,要避免在热点代码中使用Method,返回数组的方法

  • Method.invoke是一个可变长参数方法,字节码层面他的最后一个参数会是Object数组,Java编译器会在调用放出生成一个长度为传入参数数量的Object数组,切Object数组不能存储基本类型Java编译器还要对基本类型进行封装,性能开销,还占用堆内存,Gc更加频繁

最后属性的获取就相对简单一些了,方法与Method使用的都差不多,这里就不多说了,最后祝大家学习愉快

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值