超车时刻:Java反射源码解析

简介: 超车时刻:Java反射源码解析

在《一篇文章全面了解Java反射机制》中我们学习了Java反射的基本使用,这篇文章就带大家一起来看看核心源码。这可是与新手拉开差距的机会。


关于反射的类

关于反射的类是很多的,我们在基础篇中已经涉及到一部分比如:Filed、Method、Constructor。同时,还有一些我们没有看到的类,比如:AccessibleObject、ReflectionFactory、MethodAccessor等。


本篇文章我们重点介绍Method类的invoke方法的处理逻辑,这也是Java反射最核心的部分。


常见反射异常

我们在使用一些框架时经常会看到类似如下的异常:


at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

1

2

3

这类异常便是通过反射机制实现的方法,在执行Method的invoke方法时抛出的异常。


比如,在Spring的xml配置文件中配置了不存在的类时,异常堆栈便会将异常指向调用的invoke方法。


所以,当你遇到类似的异常,可以简单推断一下,你所使用的框架可能使用了反射机制。


下面,我们就来看看Method的invoke方法到底做了些什么。


源码分析

直接点击程序中调用的invoke方法,查看第一层源代码:


@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

@CallerSensitive注解:这个注解是Java修复漏洞用的。防止使用者使用双重反射来提升权限,原理是因为当时反射只检查深度的调用者的类是否有权限,本身的类是没有这么高权限的,但是可以通过多重反射来提高调用的权限。


使用该注解,getCallerClass方法就会直接跳过有 @CallerSensitive修饰的接口方法,直接查找真实的调用者(actual caller)。


在invoke方法的前半部部分主要是用来做一些检查工作,重点在于ma.invoke(obj, args)方法。这里使用到了MethodAccessor接口,该接口位于sun.reflect包下,是生成反射类的入口,此部分属于未开源部分。


在MethodAccessor中定义了invoke方法:


public interface MethodAccessor {

   Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;

}

1

2

3

该接口默认有三个实现类:


sun.reflect.DelegatingMethodAccessorImpl

sun.reflect.MethodAccessorImpl

sun.reflect.NativeMethodAccessorImpl

1

2

3

默认情况下methodAccessor值是为null的,那么看看acquireMethodAccessor方法是如何创建MethodAccessor的实现类的。


private MethodAccessor acquireMethodAccessor() {
    // First check to see if one has been created yet, and take it
    // if so
    MethodAccessor tmp = null;
    if (root != null) tmp = root.getMethodAccessor();
    if (tmp != null) {
        methodAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }
    return tmp;
}

acquireMethodAccessor方法中首先判断是否存在MethodAccessor的实例,如果存在则直接拿来使用。否则,调用ReflectionFactory的newMethodAccessor方法来创建一个,创建完成并设置到root配置中。


继续看newMethodAccessor的创建过程:


public MethodAccessor newMethodAccessor(Method var1) {
    checkInitted();
    if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
        return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
    } else {
        NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
        DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
        var2.setParent(var3);
        return var3;
    }
}

通过debug会发现,默认情况下首先进入else处理逻辑中。在else中创建了一个NativeMethodAccessorImpl对象,并作为构造参数传入了DelegatingMethodAccessorImpl的构造方法中。


这里很明显使用了代理模式(可参看《Java代理模式及动态代理详解》一文),将NativeMethodAccessorImpl对象交给 DelegatingMethodAccessorImpl对象代理。同时,通过setParent方法,NativeMethodAccessorImpl也持有了DelegatingMethodAccessorImpl的引用。


看你一下DelegatingMethodAccessorImpl的源码,你会发现它就是代理模式的标准实现:


class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;
    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }
    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }
    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }
}

NativeMethodAccessorImpl被赋值给DelegatingMethodAccessorImpl中的DelegatingMethodAccessorImpl属性,同时这两个类都实现了MethodAccessorImpl接口。而在DelegatingMethodAccessorImpl又包装了invoke方法。静态代理的标准实现方式。


经过代码跟踪,我们发现ReflectionFactory类的newMethodAccessor方法返回的是DelegatingMethodAccessorImpl类对象。那么ma.invoke()方法调用的是DelegatingMethodAccessorImpl的invoke方法。


而DelegatingMethodAccessorImpl又调用了设置的NativeMethodAccessorImpl对象的invoke方法。


public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
    if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
        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);
}

该invoke方法中首先会判断numInvocations是否会大于一个阈值,改值默认为:


private static int inflationThreshold = 15;

1

如果大于该值并且不是匿名类则会进行新的MethodAccessorImpl的创建,并且赋值给代理类DelegatingMethodAccessorImpl。也就是说创建了一个新的实现类把上面原有的实现类给替换掉了。


在MethodAccessor的具体实现中使用了Inflation(通货膨胀)机制。初次加载字节码实现反射,使用Method.invoke()和Constructor.newInstance()加载花费的时间是使用原生代码加载花费时间的3到4倍。这使得那些频繁使用反射的应用需要花费更长的启动时间。


为了避免这种加载时间的问题,在第一次加载的时候重用了JVM的入口,之后切换到字节码实现的实现。


上面我们也看到了MethodAccessor实现中有一个Native版本和Java版本。


Native版本一开始启动快,但是随着运行时间变长,速度变慢。Java版本一开始加载慢,但是随着运行时间变长,速度变快。正是因为两种存在这些问题,所以第一次加载时使用的是NativeMethodAccessorImpl,而当反射调用次数超过15次之后,则使用MethodAccessorGenerator生成的MethodAccessorImpl对象去实现反射。


最后,我们看一下整个过程的时序图。


image.png

目录
相关文章
|
12天前
|
Java 开发者
Java 函数式编程全解析:静态方法引用、实例方法引用、特定类型方法引用与构造器引用实战教程
本文介绍Java 8函数式编程中的四种方法引用:静态、实例、特定类型及构造器引用,通过简洁示例演示其用法,帮助开发者提升代码可读性与简洁性。
|
16天前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
308 100
|
21天前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
21天前
|
机器学习/深度学习 JSON Java
Java调用Python的5种实用方案:从简单到进阶的全场景解析
在机器学习与大数据融合背景下,Java与Python协同开发成为企业常见需求。本文通过真实案例解析5种主流调用方案,涵盖脚本调用到微服务架构,助力开发者根据业务场景选择最优方案,提升开发效率与系统性能。
186 0
|
21天前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
100 1
|
1月前
|
Java
Java的CAS机制深度解析
CAS(Compare-And-Swap)是并发编程中的原子操作,用于实现多线程环境下的无锁数据同步。它通过比较内存值与预期值,决定是否更新值,从而避免锁的使用。CAS广泛应用于Java的原子类和并发包中,如AtomicInteger和ConcurrentHashMap,提升了并发性能。尽管CAS具有高性能、无死锁等优点,但也存在ABA问题、循环开销大及仅支持单变量原子操作等缺点。合理使用CAS,结合实际场景选择同步机制,能有效提升程序性能。
|
2月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
2月前
|
存储 缓存 算法
Java数据类型与运算符深度解析
本文深入解析Java中容易混淆的基础知识,包括八大基本数据类型(如int、Integer)、自动装箱与拆箱机制,以及运算符(如&与&&)的使用区别。通过代码示例剖析内存布局、取值范围及常见陷阱,帮助开发者写出更高效、健壮的代码,并附有面试高频问题解析,夯实基础。
|
21天前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
59 0

热门文章

最新文章

推荐镜像

更多
  • DNS