从代理模式到mockito原理

Java代理模式深度解析:从静态代理到动态代理(JDK/CGLIB)实战 代理模式是面向对象编程中实现功能增强和访问控制的核心设计模式,其核心思想是通过一个代理对象来间接访问真实对象,从而在不修改原始代码的前提下,实现日志记录、性能监控、事务管理等横切关注点。从原理上看,代理分为静态代理和动态代理两大类。静态代理在编译期确定,需要为每个目标类手动编写代理类,适用于简单场景;而动态代理则在运行时动态生成代理类,极大地提升了灵活性和代码复用性,是现代化框架的基石技术。在Java生态中,动态代理主要有两种实现方式:JDK动态代理基于接口和反射机制,要求目标对象实现接口;CGLIB动态代 阅读详情

代理模式回顾

代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

  • 抽象角色Subject:通过接口或抽象类声明真实角色实现的业务方法。

  • 代理角色Proxy:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

  • 真实角色RealSubject:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用

静态代理是由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

根据上面代理模式的类图,可以很简单的写出静态代理的例子。

代理类在程序运行时创建的代理方式被成为动态代理。动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。

JDK的动态代理

JDK提供了Proxy类的newProxyInstance方法,可以创建一个动态代理对象

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        // Android-changed: sm is always null
        // final SecurityManager sm = System.getSecurityManager();
        // if (sm != null) {
        //     checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        // }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            // Android-changed: sm is always null
            // if (sm != null) {
            //     checkNewProxyPermission(Reflection.getCallerClass(), cl);
            // }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                // Android-changed: Removed AccessController.doPrivileged
                cons.setAccessible(true);
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
  private static Class<?> getProxyClass0(ClassLoader var0, Class... var1) {
        if (var1.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        } else {
            return (Class)proxyClassCache.get(var0, var1);
        }
    }

通过 getProxyClass0方法获取到了代理类

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache(new Proxy.KeyFactory(), new Proxy.ProxyClassFactory());
ProxyClassFactory动态生成了代理类,ProxyClassFactory调用了ProxyGenerator动态生成了代理类的字节码。

JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

如何对类进行代理呢?可以使用CGLIB

CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)

JDK和Cglib对比

  1. JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。

  2. JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。

  3. JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。

mockito最开始使用了Cglib创建动态代理,后来修改为使用bytebuddy。

mockito原理

//Mockito.mock方法
    public static <T> T mock(Class<T> classToMock, MockSettings mockSettings) {
        return MOCKITO_CORE.mock(classToMock, mockSettings);
    }
//MOCKITO_CORE.mock方法
    public <T> T mock(Class<T> typeToMock, MockSettings settings) {
        if (!MockSettingsImpl.class.isInstance(settings)) {
            throw new IllegalArgumentException("Unexpected implementation of '" + settings.getClass().getCanonicalName() + "'\n" + "At the moment, you cannot provide your own implementations of that class.");
        }
        MockSettingsImpl impl = MockSettingsImpl.class.cast(settings);
        MockCreationSettings<T> creationSettings = impl.build(typeToMock);
        T mock = createMock(creationSettings);
        mockingProgress().mockingStarted(mock, creationSettings);
        return mock;
    }
//MockUtil createMock方法
  public static <T> T createMock(MockCreationSettings<T> settings) {
        MockHandler mockHandler =  createMockHandler(settings);

        T mock = mockMaker.createMock(settings, mockHandler);

        Object spiedInstance = settings.getSpiedInstance();
        if (spiedInstance != null) {
            new LenientCopyTool().copyToMock(spiedInstance, mock);
        }

        return mock;
    }
//SubclassByteBuddyMockMaker createMock方法
   public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
        Class<? extends T> mockedProxyType = createMockType(settings);

        Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
        T mockInstance = null;
        try {
            mockInstance = instantiator.newInstance(mockedProxyType);
            MockAccess mockAccess = (MockAccess) mockInstance;
            mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));

            return ensureMockIsAssignableToMockedType(settings, mockInstance);
        } catch (ClassCastException cce) {
            throw new MockitoException(join(
                    "ClassCastException occurred while creating the mockito mock :",
                    "  class to mock : " + describeClass(settings.getTypeToMock()),
                    "  created class : " + describeClass(mockedProxyType),
                    "  proxy instance class : " + describeClass(mockInstance),
                    "  instance creation by : " + instantiator.getClass().getSimpleName(),
                    "",
                    "You might experience classloading issues, please ask the mockito mailing-list.",
                    ""
            ), cce);
        } catch (org.mockito.creation.instance.InstantiationException e) {
            throw new MockitoException("Unable to create mock instance of type '" + mockedProxyType.getSuperclass().getSimpleName() + "'", e);
        }
    }
//SubclassByteBuddyMockMaker createMockType方法
    public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
        try {
            return cachingMockBytecodeGenerator.mockClass(MockFeatures.withMockFeatures(
                    settings.getTypeToMock(),
                    settings.getExtraInterfaces(),
                    settings.getSerializableMode(),
                    settings.isStripAnnotations()
            ));
        } catch (Exception bytecodeGenerationFailed) {
            throw prettifyFailure(settings, bytecodeGenerationFailed);
        }
    }
//SubclassBytecodeGenerator mockClass方法
 public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
        String name = nameFor(features.mockedType);
        DynamicType.Builder<T> builder =
                byteBuddy.subclass(features.mockedType)
                         .name(name)
                         .ignoreAlso(isGroovyMethod())
                         .annotateType(features.stripAnnotations
                             ? new Annotation[0]
                             : features.mockedType.getAnnotations())
                         .implement(new ArrayList<Type>(features.interfaces))
                         .method(matcher)
                           .intercept(dispatcher)
                           .transform(withModifiers(SynchronizationState.PLAIN))
                           .attribute(features.stripAnnotations
                               ? MethodAttributeAppender.NoOp.INSTANCE
                               : INCLUDING_RECEIVER)
                         .method(isHashCode())
                           .intercept(hashCode)
                         .method(isEquals())
                           .intercept(equals)
                         .serialVersionUid(42L)
                         .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)
                         .implement(MockAccess.class)
                           .intercept(FieldAccessor.ofBeanProperty());
        if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) {
            builder = builder.implement(CrossClassLoaderSerializableMock.class)
                             .intercept(writeReplace);
        }
        if (readReplace != null) {
            builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE)
                    .withParameters(ObjectInputStream.class)
                    .throwing(ClassNotFoundException.class, IOException.class)
                    .intercept(readReplace);
        }
        ClassLoader classLoader = new MultipleParentClassLoader.Builder()
            .append(features.mockedType)
            .append(features.interfaces)
            .append(currentThread().getContextClassLoader())
            .append(MockAccess.class, DispatcherDefaultingToRealMethod.class)
            .append(MockMethodInterceptor.class,
                MockMethodInterceptor.ForHashCode.class,
                MockMethodInterceptor.ForEquals.class).build(MockMethodInterceptor.class.getClassLoader());
        if (classLoader != features.mockedType.getClassLoader()) {
            assertVisibility(features.mockedType);
            for (Class<?> iFace : features.interfaces) {
                assertVisibility(iFace);
            }
            builder = builder.ignoreAlso(isPackagePrivate()
                .or(returns(isPackagePrivate()))
                .or(hasParameters(whereAny(hasType(isPackagePrivate())))));
        }
        return builder.make()
                      .load(classLoader, loader.resolveStrategy(features.mockedType, classLoader, name.startsWith(CODEGEN_PACKAGE)))
                      .getLoaded();
    }

最终mockito 在SubclassBytecodeGenerator类中使用bytebuddy 动态创建了一个代理类,bytebuddy提供了一个API用于动态生成任意的Java类。

动态代理使用了继承,所以不能代理final类。

mockito2.0开始支持代理final类,它是如何实现的呢?

2.0引入新的插件InlineByteBuddyMockMaker,在InlineBytecodeGenerator中进行字节码转换,原理是使用JAVA instrumentation动态修改类的行为(注意这里不是Android的instrumentation)。

Android中不能使用 inline插件,因为JAVA的 java.lang.instrument Android中是不可见的,会抛出NoClassDefFoundError异常

 

JavaAgent实现无侵入Mock:TestableMock原理与实战 在Java单元测试中,Mock技术通过隔离依赖对象来构造特定测试场景,是保证代码质量的核心手段。其原理在于利用代理、反射或字节码操作等技术,在运行时替换或拦截方法调用,从而模拟外部依赖的行为。这项技术的核心价值在于提升测试的独立性和执行速度,使开发者能够在不依赖数据库、网络服务等外部组件的情况下,快速验证业务逻辑的正确性。在微服务架构和复杂系统测试中,Mock的应用尤为广泛,能够有效应对第三方接口、遗留代码等难以测试的场景。本文以TestableMock为例,深入解析其基于JavaAgent和字节码编织的无 阅读详情

相关推荐

SpringBoot与Mockito测试框架

1. 背景介绍 1.1 什么是SpringBoot SpringBoot是一个基于Spring框架的开源项目,旨在简化Spring应用程序的创建、配置和部署。它提供了一种快速开发的方法,使得开发者可以专注于编写业务逻辑,而不需要关心底层的配置和依赖管理。SpringBoot的核心思想是约定

AI天才研究院 453

Robolectric单元测试 - PowerMock导致的ClassCastException

Bug描述:单元测试的时候,多个类同时Mock了一个工具类,每个类单独测试的时候是好的,但是整个项目测试生成测试报告的时候,只有第一个测试通会通过,其他的就提示ClassCastException错误。 仔细看报错会有这么一句: You might experience classloading issues, disabling the Objenesis cache might help (see MockitoConfiguration)" 原因是这个类用了不同的类加载器加载,我个人理解是第一个测试类

孔小乐的博客 999

别再为电路自激发愁了:手把手教你用吸波材料搞定微波腔体谐振(附HFSS仿真设置)

本文详细解析了微波腔体谐振问题的工程解决方案,重点介绍了吸波材料的选型、安装工艺及HFSS仿真设置。通过实际案例和计算工具,帮助射频工程师有效抑制腔体自激,提升系统稳定性。特别适用于35GHz毫米波雷达等高频电路设计场景。

weixin_42662293的博客 383

Mockito简单使用及原理分析

springboot+junit+mockito做单元测试,真香

seasonLai的博客 1万+

昇腾910B+MindIE部署Qwen模型避坑指南:从镜像下载到推理测试全流程

本文详细介绍了在昇腾910B硬件环境中使用MindIE推理工具部署Qwen系列模型的全流程指南。从环境准备、镜像下载到模型部署与性能优化,提供一站式解决方案,帮助开发者高效完成AI模型推理任务。

weixin_29219189的博客 766

STM32G030C8T6 ADC+DMA实战:同时采集外部电压和芯片温度的完整代码流程

本文详细介绍了STM32G030C8T6芯片通过ADC+DMA实现外部电压和芯片内部温度采集的完整流程。从硬件设计到CubeMX配置,再到驱动模块化实现与系统优化,提供了高精度多通道采集系统的实战指南,特别适合嵌入式开发者在工业控制和环境监测项目中应用。

weixin_42653691的博客 467

powermock源码浅析

一.基本构成 二.初始化过程  通过初始化MockClassLoader并通过该ClassLoader加载配置的@PrepareForTest类,通过javassit修改对应类的字节码,在方法调前加上类似的 Object localObject = MockGateway.methodCall(this, "testFinal", new Object[0], Desc.getParams

烈冬冰夏的博客 2201

机器学习模型系列:随机森林的原理和示例介绍

让我们总结一下与其他监督式机器学习模型相比,随机森林的优缺点。优点在许多真实数据集上已知能提供高度准确的模型。通过结合多个决策树的预测,可以捕捉数据集中的复杂交互和模式。通过自动选择相关特征,可以有效处理高维数据集。与单个决策树相比,不容易过拟合。自助采样和每个节点的随机特征选择有助于减少过拟合并改善泛化能力。可以处理包括数值和分类特征在内的异构数据类型。可以处理缺失值而不需要填充。提供特征重要性的度量。集成中的树可以并行训练,因为每个决策树可以独立构建。模型的泛化误差具有理论上的界限。

数智笔记 1万+

Mockito 源码分析

本文主要以 Mockito 2.9.0 为基础进行分析,如有bug请大家指正。 Mockito 主要过程如下图所示: Mockito架构 如上图所示,通过对目标类进行创建 proxy 类,并添加相应的函数拦截方式,然后实例化,即为最终生成的 mock 对象。 通过 Interceptor 类来对目...

weixin_33738555的博客 408

从零到一:AIC8800 USB-WiFi模块在Linux内核6.x下的驱动适配与避坑指南

本文详细介绍了AIC8800 USB-WiFi模块在Linux内核6.x下的驱动适配全流程,包括环境准备、源码编译、内核API变更处理及常见问题解决方案。重点针对驱动适配中的符号导出、设备ID识别和固件加载等核心挑战提供实用指南,帮助开发者高效完成硬件驱动兼容性调试。

k8l9m0n的博客 1022

如何使用Google Maps API爬取地理数据:详细教程与Python实现

Google Maps API 是一组服务的集合,允许开发者访问地图、地点、路线等地理信息。:将地址转换为地理坐标,或将地理坐标转换为地址。:计算两点之间的行车路线、步行路线等。Places API:获取附近的地点、商店、餐馆等。:计算多个地点之间的距离和时间。在接下来的代码中,我们将结合这些 API,展示如何获取并处理地理数据。本文详细介绍了如何使用 Google Maps API 获取地理数据,涵盖了从地址转经纬度、获取地点详情、计算路线、计算多个地点的距离与时间等多个功能。

2201_76125261的博客 733

程序员必看| mockito原理浅析

戳蓝字「TopCoder」关注我们哦!编者注:mockito是一个单测mock框架,上手简单,Java开发人员必备工具~mockito官网:http://mockito....

luoxn28的专栏 4779

云原生之容器编排实践-在K8S集群中使用Registry2搭建私有镜像仓库

基于前面搭建的3节点 Kubernetes 集群,今天我们使用 Registry2 搭建私有镜像仓库,这在镜像安全性以及离线环境下运维等方面具有重要意义。作为测试环境,本次使用 Registry2 搭建了私有镜像仓库,实际生产环境建议使用 Harbor;创建了一个 local-storage 的 StorageClass ,并使用本地磁盘的方式创建使用 PV ,实际建议使用 NFS 。

Heartsuit的博客 2460

mockito junit_mockito原理浅析

mockito是一个单测mock框架,上手简单,mockito官网:http://mockito.org,API文档:http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html,项目源码:https://github.com/mockito/mockito。 mockito基础在写单元测试的过程中,我们往往会遇到要测试的类有很多依赖...

weixin_39989159的博客 470

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Mockito核心源码分析总结

Mockito核心源码分析总结 Mockito使用示例 在开始看mockito的源码之前,首先看一下下面两个mockito的使用示例。 简单使用示例 @Test public void mockitoTest(){ // 1.生成一个mock对象 List<String> mockedList = Mockito.mock(List.class); // 查看mock对象的类名: $java.util.List$$Enha

Blossom 1857

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

动手编写 Mockito

动手编写 Mockito 初次使用 Mockito,能够感受到它的神奇,尤其是这样的语法: when(cal.add(0, 1)).thenReturn(1); Mockito 会把它理解成,当 cal 调用 add 方法且参数为 0 和 1 时,则返回 1。 我们知道,java 中的程序调用是以栈的形式实现的,对于 when 方法,add 方法的调用对它是不可见的。when 能

Stroller 515

Java 23 种设计模式:从踩坑到精通 | 代理模式 —— 你的 AOP 就是用代理实现的

摘要:本文深入剖析了 Java 代理模式(Proxy Pattern)的三种实现形态(静态代理、JDK 动态代理、CGLIB 动态代理),并结合 Spring AOP 源码详细解读了代理机制的选择逻辑与自调用失效陷阱。文章从报表查询的性能优化场景切入,不仅涵盖了 MyBatis Mapper、RPC 框架等经典应用,还对比了装饰器模式,梳理了高频面试题,是 Java 开发者掌握“面向切面编程”基石的实战指南。

折哥|智能物流与Java实战,副高数据库工程师。深耕出版社物流WMS/OMS(线体/立库/VGA),也分享Java进阶与OMS电商对接。Oracle→PG实战派。相信沉淀可迁移能力,分享通用技术,不藏不虚。 1069

Spring/Boot/Cloud系列知识(3)— — 代理模式(中)

本文转自:https://blog.youkuaiyun.com/yinwenjie/article/details/78008276 (接上文《Spring/Boot/Cloud系列知识(2)——代理模式》) 2.2、代理模式在Spring中的应用 那么java中基于java.lang.reflect.Proxy的动态代理模式和Spring生态有什么关系呢?Spring中的所有Bean实例存储在一...

Welcome to loveuui's blog 374

Spring/Boot/Cloud系列知识(4)——代理模式(下)

============================================= (接上文《Spring/Boo...

140

Spring/Boot/Cloud系列知识(3)——代理模式(中)

代理模式是23种设计模式中的一种,属于一种结构模式。用一句大白话解释这个设计模式:当外部调用者调用A来完成一件事情/一个动作时,并不直接调用A本身,而是调用一个代理者,并再由代理者负责调用真实的执行者A,最终达到间接调用的目的。

JAVA入门中 5665

【Java设计模式】动态代理模式:实现无缝对象拦截

动态代理设计模式旨在提供一种灵活的代理机制,能够在运行时为各种接口动态创建代理,从而实现对对象的受控访问或功能增强。

道长的博客 1794

【Mock技术详解:从概念到实践的全面指南】

Mock技术详解:从概念到实践的全面指南

XiaoMu_001的博客 1429

认识mock

背景 如果写单元测试,mock必不可少;Mock是解决单元测试UT里依赖问题的重要手段;其实mock是个很简单的概念,不要害怕。 代码有依赖(not self contained) 单元测试目的是只测试某个模块,不需要测试他的依赖;但是实际工作中,肯定存在依赖的问题,controller和service,dao都是有依赖的。 如果我们只单元测试controller,但是不希望同时测试servic...

mike_learns_to_rock的博客 220

Java static关键字详解:原理、应用与面试要点

static关键字是Java语言中实现类级别共享的核心机制,涉及JVM内存模型、类加载等底层原理。静态变量存储在方法区而非堆内存,具有全局唯一性,常用于实现工具类、单例模式等场景。在并发编程中需特别注意静态变量的线程安全问题,合理使用AtomicInteger等并发工具。从字节码层面看,静态方法使用invokestatic指令调用,与实例方法的invokevirtual有本质区别。掌握static关键字的正确用法,能够优化系统性能并避免内存泄漏等问题,是Java开发者必须扎实掌握的基础知识。

weixin_34185320的博客 395

Java云同桌学习系列(二十五)——单元测试

单元测试框架 Mockito 与 PowerMock介绍

qq_41677788的博客 1103
上一篇: lambda表达式介绍以及Android Studio引入lambda
下一篇: Android事件的产生和底层分发
robert_chao
博客等级 码龄15年 265粉丝 119原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值