Mockito如何在不调用构造方法的情况下实例化类?
Mockito无构造方法调用实例化的核心机制
1. 内联Mock的字节码修改逻辑
Mockito当前默认采用内联Mock生成器(InlineByteBuddyMockMaker),而非传统的子类代理模式:
- 它会生成一个与目标类同名的动态修改类,因此
mock.getClass()会返回原类的全限定名,但实际是字节码被修改后的类实例 - 动态替换目标类的所有非final方法实现(比如你调试时看到的自定义
toString(),格式为MyClass mock, hascode 123456,就是Mockito动态生成的) - 核心关键:直接绕过目标类的构造方法调用,跳过原构造的执行逻辑
2. 实例化的底层:MethodHandle与JVM原生支持
你调试到的MethodHandle.invokeExact()是实现构造绕过的核心,这是JVM提供的本地方法,直接操作对象创建的底层流程:
- Mockito通过
MethodHandles.Lookup获取构造方法的句柄,但并非直接调用构造方法 - 它利用JVM对象分配与构造分离的特性:先通过底层API分配对象内存(类似
Unsafe.allocateInstance但更合规),完全跳过原构造方法的执行,直接初始化Mockito代理所需的字段和逻辑 - 你看到
newInstance(type)未抛出异常,正是因为走了这条绕过构造的路径,没有触发目标类构造方法里的UnsupportedOperationException
3. 为什么没用到Objenesis?
Objenesis是旧版Mockito用于绕过构造的第三方工具,新版Mockito通过内联Mock+MethodHandle的组合,直接借助JVM合法API实现构造绕过,无需依赖Objenesis:
- 只有当内联方式失败时(比如特殊类加载器、模块化环境限制等场景),才会降级到Objenesis或
Unsafe方案 - 你的调试流程中
newInstance(type)成功执行,说明当前环境支持内联构造绕过逻辑
4. 字节码修改的具体操作细节
Mockito借助ByteBuddy完成的字节码修改包括:
- 为目标类添加Mockito代理相关的字段,用于存储mock的行为配置、调用记录等
- 重写所有非final方法,替换为Mockito的代理逻辑(比如方法调用时优先触发预定义的stub逻辑)
- 修改类的初始化流程,跳过原构造方法的执行步骤,直接完成代理实例的初始化
针对你疑问的逐一解答
- 为什么
getClass()返回原类名?:内联Mock生成的动态类与原类同名,类加载器会加载这个修改后的类,因此返回的类名与原类一致 - 原构造方法还存在吗?:原构造方法依然存在,但Mockito在实例化时完全跳过了它的调用,没有执行构造方法内的代码
- Mock到底实例化的是什么?:实例化的是字节码被修改后的原类变体,继承关系并未改变,只是类的字节码被动态修改添加了代理逻辑
内容的提问来源于stack exchange,提问作者Sergey Zolotarev
相关产品推荐
相关产品推荐

