常见面试题-JDK和CGLIB动态代理

JDK 动态代理和 CGLIB 动态代理对比

  1. JDK 动态代理只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。另外CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final 类型的类和方法
  2. 就二者的效率来说,大部分情况都是JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
  3. JDK 动态代理利用了拦截器、反射机制生成一个代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理;CGLIB 动态代理利用了 ASM 框架,将代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理

JDK动态代理底层原理:

假如目前有一个接口 HelloService、实现类HelloServiceImpl(包含一个 say() 方法,需要被增强)、增强类MyInvocationHandler

在 JDK 动态代理中,生成的代理类 $Proxy1 是继承 Proxy 并且实现 HelloService 接口,当调用代理类的方法时,会进入到拦截器 MyInvocationHandler 的 invoke 方法中,下边为代理类生成代码:

// 生成代理对象
HelloService helloService = (HelloService) Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(), new Class[]{HelloService.class}, new MyInvocationHandler());
helloService.say();

通过上述代码拿到的 helloService 对象其实就是 JDK 动态代理对象,我们可以通过添加 VM options 来将动态代理对象保存下来,添加 VM options 如下:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

之后生成的动态代理对象如下(这里为了更直观的看代理类,因此只保留了最关键的代码),say() 其实就是定义在 HelloService 中需要被增强的方法,那么当调用 helloService.say() 时,其实就是调用 $Proxy1.say() 方法,在该方法中会调用 h.invoke() 方法,这里的 h 就是我们自己定义的 MyInvocationHandler 拦截器,之后就会进入到拦截器的 invoke 方法,

import com.example.nettystudy.JdkProxyTest.HelloService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy1 extends Proxy implements HelloService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    ...
    
    public final void say() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    ...
}

下边来看一下拦截器的 invoke 方法,该方法有 3 个参数,第一个参数 proxy 也就是上边的代理类对象, method 就是接口中的 say 方法,那么在拦截器中就会执行我们自己添加的增强操作了

public class MyInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法执行前");
        // 这里 HelloServiceImpl 是被代理对象,被代理对象执行方法
        Object result = method.invoke(new HelloServiceImpl(), args);
        System.out.println("方法执行后");
        return result;
    }
}

cglib动态代理底层原理

cglib 采用底层的字节码技术,为一个类创建子类,并且在子类中使用方法去拦截所有的父类调用,并织入横切逻辑

cglib 使用如下:

// Human.java
public class Human {
    public void info() {
        System.out.println("Human invoke info");
    }
    public void fly() {
        System.out.println("Human invoke fly");
    }
}

// CGLibProxy.java  拦截器
class CGLibProxy implements MethodInterceptor {
     
  
	 // CGLib需要代理的目标对象 
    private Object targetObject;
  
    public Object getProxyInstance(Object obj) { 
     
        this.targetObject = obj;  
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        // 2.设置父类--可以是类或者接口
        enhancer.setSuperclass(obj.getClass());  
        //3. 设置回调函数
        enhancer.setCallback(this);  
        //4. 创建子类对象,即代理对象
        Object proxyObj = enhancer.create();  
        // 返回代理对象 
        return proxyObj;
    }  
  
    public Object intercept(Object proxy, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        System.out.println("方法执行前增强处理");
        // 执行目标目标对象方法
        Object obj = method.invoke(targetObject, args);
        System.out.println("方法执行后增强处理");
        return obj;
    }  
}

// TestCglibProxy.java 测试类
public class TestCglibProxy {
	public static void main(String[] args) {
		// 创建被代理对象
		Human man = new Human();
		// 添加如下代码,获取代理类源文件
		String path = CGLibProxy.class.getResource(".").getPath();
		System.out.println(path);
		System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);

		CGLibProxy cgLibProxy = new CGLibProxy();
		Object obj = cgLibProxy.getProxyInstance(man);
		System.out.println(obj.getClass());
		Human hu = (Human)obj;
		hu.info();
		hu.fly();
	}
}

上边程序输出为:

在这里插入图片描述

可以根据红色输出的路径找到我们生成的代理类的 class 文件

在这个 Human 类,也就是需要被增强的类中,我们定义了两个方法 info()、fly(),那么 cglib 生成的子类会继承 Human 类,并且重写这两个方法,生成的代理类如下:

在代理类中,会先将拦截器赋值给 var10000,之后再调用 var10000.intercept 这个方法,也就是我们自己定义的拦截器的拦截方法CGLibProxy#intercept()

public class Human$$EnhancerByCGLIB$$a1812f09 extends Human implements Factory {
	// ...    省略其余代码
    public final void info() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$info$0$Method, CGLIB$emptyArgs, CGLIB$info$0$Proxy);
        } else {
            super.info();
        }
    }
    // ...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/135636.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unbuntu安装、测试和卸载gcc11

GCC 可用于编译 C、C,本文介绍如何 Ubuntu 上安装 gcc11、测试和卸载它。 1. 在Ubuntu 上安装 gcc11 添加工具链存储库 sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test在 Ubuntu 上安装 gcc11 sudo apt install -y gcc-11验证 gcc11 版本 gcc-11 --v…

AI:80-基于深度学习的医学图像分割与病变识别

🚀 本文选自专栏:人工智能领域200例教程专栏 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的代码,详细讲解供大家学习,希望可以帮到大家。欢迎订阅支持,正在不断更新中,…

数据架构与数据模型

数据架构: 待定 数据模型: 数据模型是对现实世界数据特征的抽象,用于描述一组数据的概念和定义。数据模型从抽象层次上描述了数据的静态特征、动态行为和约束条件。数据模型所描述的内容有三部分,分别是数据结构、数据操作和数…

数据结构与算法 | 第四章:字符串

本文参考网课为 数据结构与算法 1 第四章字符串,主讲人 张铭 、王腾蛟 、赵海燕 、宋国杰 、邹磊 、黄群。 本文使用IDE为 Clion,开发环境 C14。 更新:2023 / 11 / 12 数据结构与算法 | 第四章:字符串 字符串概念字符串字符字符…

rocksdb中测试工具Benchmark.sh用法(基准、性能测试)

1.首先要安装db_bench工具,这个工具在成功安装rocksdb之后就自动存在了,主要是在使用make命令之后就成功安装了,详情请见我之前的文章 2.确保成功安装db_bench之后,找到安装的rocksdb目录下面的tools文件夹,查看里面是…

如何让VirtualBox系统使用Ubuntu主机的USB

如何让VirtualBox系统使用Ubuntu主机的USB 当通过 VirtualBox 尝试不同的操作系统时,访问虚拟机中的 USB 驱动器来传输数据非常有用。 安装Guest Additions 自行百度安装Guest Additions的方法,最终的效果如下: 将用户添加到 vboxusers 组…

前端面试题 计算机网络

文章目录 ios 7层协议tcp协议和udp协议的区别tcp协议如何确保数据的可靠http和tcp的关系url输入地址到呈现网页有哪些步骤post和get本质区别,什么时候会触发二次预检GET请求:POST请求:触发二次预检(CORS中的预检请求)&…

通过结构间比值比较迭代次数

( A, B )---3-30-2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点,A有5个点,B全是0,排列组合。让A,B训练集分别有3,4,5,6张图片,统计迭代次数并排序。 先比较图片数量是3和4的情况 n4 迭代次数…

移植LVGL到单片机的一个demo简单介绍

简介 背景: 本文使用的是主控IC为stm32f103zet6, 显示IC为ST7735s,它是128*160的像素,色深为RGB565颜色。 官方虽然说LVGL移植平台只需 64kB 闪存和 8kB RAM 就足以满足简单的用户界面。但我移植到stm32f103c8t6,不管怎么修改配…

【数据结构】入队序列出队序列问题(以21年408真题举例)

题型说明 一般是一个队列,其中一边可以入队,另一边可以入队和出队只可入队的含义是从这个方向是以队列形式存在可以入队和出队表示此边以堆形式存在 怎么分析? 以21年408真题举例 考点分析 出队序列存在两种情况:入之后就出&…

是谁为所欲为,将我的电脑控作己用?

在刚刚发完短篇小杂文《要找事做,我真怕被闲死》的投稿之后,笔者继续浏览社交网站的网页搜索...... 正看到《温州殡仪馆 》《温州动车723事故死亡高 》《 动车脱轨温州事件真正原因》《 浙江平阳县灭门惨案处理结果公布》《 温州厉秀珍死亡 》这一串又一…

从HDFS到对象存储,抛弃Hadoop,数据湖才能重获新生?

Hadoop与数据湖的关系 1、Hadoop时代的落幕2、Databricks和Snowflake做对了什么3、Hadoop与对象存储(OSD)4、Databricks与Snowflake为什么选择对象存储5、对象存储面临的挑战 1、Hadoop时代的落幕 十几年前,Hadoop是解决大规模数据分析的“白…

Qt开发流程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:…

SpringBoot整合第三方技术

SpringBoot整合JUnit 名称:SpringBootTest 类型:测试类注解 位置:测试类定义上方 作用:设置JUnit加载的SpringBoot启动类SpringBootTest(classes Springboot05JUnitApplication.class) class Springboot07JUnitApplicationTests…

腾讯云3年轻量2核4G5M服务器756元,抓紧数量不多

腾讯云轻量应用服务器特价是有新用户限制的,所以阿腾云建议大家选择3年期轻量应用服务器,一劳永逸,免去续费困扰。腾讯云轻量应用服务器3年可以选择2核2G4M和2核4G5M带宽,3年轻量2核2G4M服务器540元,2核4G5M轻量应用服…

redis学习指南--概览篇

redis怎么学 官方学习网站: redis.cn 1、整体了解redis redis是一个内存数据库、kv数据库,数据结构数据库,redis中数据都是存储在redis中,可以通过key查找value,value可以有多种数据结构,有:…

归并外排序实现

文章目录 1. 海量数据排序 1. 海量数据排序 如果我们想在文件中海量数据排序,我们比较适合选用归并排序。 首先,我们要看要排序的文件的大小,比如说这个文件是10G,而我们的内存是1G,那么我们可以把文件切成10份。这样…

汽车操纵稳定性matlab仿真

1、内容简介 略 14-可以交流、咨询、答疑 2、内容说明 汽车操纵稳定性matlab仿真,包含完整的论文 操纵动力学、两自由度 摘要:当今,仿真技术日益广泛地应用于汽车工程领域,操纵稳定性研究越来越多地使用成熟的计算机仿真理论…

【LeetCode:715. Range 模块 | 线段树】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

unity line renderer绘制的颜色不是想要的红色

线条不是暗红色的,用的是默认的红色 将材质选则为如下即可