都2024年了,还有人不懂动态代理么?
文章目录
- 一、定义
 - 二、静态代理
 - 三、动态代理
 - 1. JDK代理
 - 1.1 JDK代理实现流程
 - 1.2 动态生成的类字节码
 
- 2. Cglib代理
 - 2.1 Cglib实现流程
 
- 四、总结
 
一、定义
静态代理和动态代理都反映了一个代理模式,代理模式是一种经典的设计模式,常用于为其他对象提供一种代理或占位符,以限制对它的访问,充当访问对象的中介。
 就好比平时我们租房,会和中介进行沟通,中介带我们去看房,中介又与房东保持联系,但房东不会带我们看房。
二、静态代理
静态代理是一种代理模式的实现,静态代理在编译时就确定了代理类和目标类之间的关系。
 在静态代理中,代理类和目标类通常实现相同的接口,或者代理类继承目标类。
 代理类作为目标类的包装,持有目标类的引用,并且调用目标对象的方法前后进行增强操作。
 下面举个例子
public interface Landlord {/*** 出租房间*/void rentingHouse();}
 
public class ShenZhenLandlord implements  Landlord {@Overridepublic void rentingHouse() {System.out.println("深圳房东出租房间");}
}
 
public class Intermediary implements Landlord{/*** 代理类*/private final Landlord landlord;public Intermediary(Landlord landlord) {this.landlord = landlord;}@Overridepublic void rentingHouse() {System.out.println("中介带租客看房");landlord.rentingHouse();System.out.println("中介收取出租非");}
}
 
public static void main(String[] args) {// 目标类Landlord landlord = new ShenZhenLandlord();//代理类Intermediary intermediary = new Intermediary(landlord);// 运行代理方法intermediary.rentingHouse();
}
 

这是一个经典的代理模式的实现,可以在不修改原对象的情况下对目标类进行增强处理,但是若接口新增方法,所有代理类都都需要新增对应的实现,不好维护。
三、动态代理
动态代理是一个在运行期间动态创建代理对象的技术,它允许开发者为一个或多个接口创建一个代理对象,且无需事先知道具体实现类。
静态代理和动态代理的区别在于class文件是编译期间确定还是运行期间确定
静态代理在编写代码的时候就明确了代理类和目标类之间的关系,在编译阶段会生成一个Class对象。
动态代理则是在运行阶段动态生成代理对象,在运行的时候动态生成字节码,并加载到JVM中去
1. JDK代理
JDK代理技术是Java提供的一种动态代理技术,使用方式如下
public class ProxyFactory {/*** 目标类*/private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxyInstance() {return Proxy.newProxyInstance(// 目标类加载器target.getClass().getClassLoader(),// 目标对象的接口类型target.getClass().getInterfaces(),// 事件处理new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("前置增强");method.invoke(target, args);System.out.println("后置增强");return null;}});}
}
 
public static void main(String[] args) {ShenZhenLandlord landlord = new ShenZhenLandlord();System.out.println(landlord.getClass());Landlord proxyInstance = (Landlord) new ProxyFactory(landlord).getProxyInstance();proxyInstance.rentingHouse();System.out.println(proxyInstance.getClass());
}
 

代理类打印出的Class为com.sun.proxy.$Proxy0, 从ShenZhenLandlord到 com.sun.proxy.$Proxy0 ,其中经历了什么呢?
1.1 JDK代理实现流程
需要知道的是JVM虚拟机的类加载过程分为加载、验证、准备、解析、初始化这五个阶段, 在加载阶段需要进行下面这几个步骤:
- 通过一个类的全限定名获取定义此类的二进制字节流
 - 将这个字节流所代表的静态结构转化为方法区的运行时数据结构
 - 内存生成一个代表这类的Class对象,作为方法区这个类的各种数据访问入口
 
而获取类的二进制字节流,JVM提供了三种途径:
- 本地获取字节码,比如前面的ShenZhenLandlord类,编译后就属于本地的字节码
 - 从网络中获取,可以使用URLClassLoader来加载类
 - 运行时计算生成,在程序运行的过程中动态生成类字节码
 
而代理就是运行时动态生成字节码,然后交给JVM进行类加载过程使用。
 所以经过JDK代理后,从ShenZhenLandlord变成了com.sun.proxy.$Proxy0,也就是程序在运行中通过计算,生成了$Proxy0代理类的类字节码。
1.2 动态生成的类字节码
由于动态生成的类字节码是动态计算出来并加载到JVM内存中去,因此无法通过查看编译后的文件去查看这个代理类的 Class文件。
 不过,可以通过arthas工具来查看动态生成的类字节码。
 下载官网:https://arthas.aliyun.com/doc/download.html#%E7%94%A8-arthas-boot-%E5%90%AF%E5%8A%A8
 输入命令java -jar arthas-boot.jar 运行arthas

proxy.jdk.Main 就是我们要查看的目标类,按3进入监控界面,如下图所示就代表进入成功了

输入命令jad com.sun.proxy.$Proxy0命令,就能解析出$Proxy0的源码了

整理一下代码,如下:
public final class $Proxy0
extends Proxy
implements Landlord {private static Method m1;private static Method m3;private static Method m2;private static Method m0;// 构造参数为InvocationHandlerpublic $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m3 = Class.forName("proxy.static_proxy.Landlord").getMethod("rentingHouse", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}// Landlord接口的`rentingHouse`方法public final void rentingHouse() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}
 
看一个类,首先先看构造方法,KaTeX parse error: Expected 'EOF', got '#' at position 70: …就是`ProxyFactory#̲getProxyInstanc…Proxy0`的构造方法传进去的,也就是是同一个!!
    public Object getProxyInstance() {return Proxy.newProxyInstance(// 目标类加载器target.getClass().getClassLoader(),// 目标对象的接口类型target.getClass().getInterfaces(),// 事件处理new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("前置增强");method.invoke(target, args);System.out.println("后置增强");return null;}});}
 
我们稍微看一下Proxy的源码
private static final Class<?>[] constructorParams ={ InvocationHandler.class };public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
{final Class<?>[] intfs = interfaces.clone();/** 获取动态生成的字节码*/Class<?> cl = getProxyClass0(loader, intfs);/** 获取这个代理类的构造参数为InvocationHandler的构造方法*/final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}// 将h作为构造参数创建代理类return cons.newInstance(new Object[]{h});
}
 
那么在$Proxy0#rentingHouse方法调用了构造参数传进来的InvocationHandler的invoke方法,也就是调用了下面这个方法(增强后的方法)
new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("前置增强");method.invoke(target, args);System.out.println("后置增强");return null;}
}
 
通过这,就可以得出下面的结论:
- 代理类实际上就是实现了
Landlord接口,重写了rentingHouse方法 - 当外界调用代理类的
rentingHouse方法的时候,实际上就是调用了new InvocationHandler的invoke方法 
2. Cglib代理
Cglib(Code Generation Library)是一个强大的高性能代码生成库,用于在运行时扩展Java类和实现接口。它通过字节码技术动态生成新的类,从而实现对目标类的代理。Cglib代理是AOP(面向切面编程)中常用的一种代理方式,尤其是在需要代理那些没有实现接口的类时。
 举个例子:
 目标类如下
public class UserServiceImpl {public String selectList(){System.out.println("正在查询");return "小明, 小红, 小黄";}
}
 
public class UserLogProxy implements MethodInterceptor {/*** 生成 CGLIB 动态代理类方法** @param target* @return*/public Object getLogProxy(Object target) {// 增强器类,用来创建动态代理类Enhancer enhancer = new Enhancer();// 设置代理类的父类字节码对象enhancer.setSuperclass(target.getClass());// 设置回调enhancer.setCallback(this);// 创建动态代理对象并返回return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("开始查询");// 执行原始方法Object result = methodProxy.invokeSuper(o, objects);System.out.println("查询完毕");return result;}
}public class MethodProxy{public Object invokeSuper(Object obj, Object[] args) throws Throwable {try {init();FastClassInfo fci = fastClassInfo;return fci.f2.invoke(fci.i2, obj, args);} catch (InvocationTargetException e) {throw e.getTargetException();}}
}
 
public class Main {public static void main(String[] args) {// 目标对象UserServiceImpl userService = new UserServiceImpl();System.out.println(userService.getClass());// 代理对象UserServiceImpl proxy = (UserServiceImpl) new UserLogProxy().getLogProxy(userService);System.out.println(proxy.getClass());String userList = proxy.selectList();System.out.println(userList);while (true) {}}
}
 

2.1 Cglib实现流程
使用arthas工具,将proxy.cglib.UserServiceImpl$$EnhancerByCGLIB$$b96d19a3类的代码获取出来。
public class UserServiceImpl$$EnhancerByCGLIB$$b96d19a3
extends UserServiceImpl
implements Factory {private MethodInterceptor CGLIB$CALLBACK_0;public final String selectList() {// 是否设置了回调MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {UserServiceImpl$$EnhancerByCGLIB$$b96d19a3.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}// 设置回调,需要调用 intercept 方法if (methodInterceptor != null) {return (String)methodInterceptor.intercept(this, CGLIB$selectList$0$Method, CGLIB$emptyArgs, CGLIB$selectList$0$Proxy);}// 无回调,调用父类的 findUserList 即可return super.selectList();}}
 
在JVM编译阶段,Enhancer会根据目标类信息去动态生成代理类并设置回调。
 当用户通过Cglib动态代理执行selectList方法的时候,会直接调用methodInterceptor.intercept方法,在intercept方法中通过invikeSuper调用父类的selectList方法。
 如果没有设置回调,则直接调用父类的selectList方法。
四、总结
JDK代理与Cglib代理对比
- Cglib实现的动态代理是基于ASM字节码生成框架实现的,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高一点。但是需要注意,Cglib代理技术不可对final类进行类或方法的代理,因为Cglib的原理是动态生成代理类的子类实现的。
 - 在JDK1.6以后的版本已经开始对JDK代理进行优化(具体优化了什么 以后有机会再写篇文章讲),在调用次数较少的情况,JDK代理的效率要比Cglib代理效率要高,只有进行大量调用的时候,Cglib的优势才会出现。而到了JDK1.8,JDK代理的效率已经高于Cglib代理,因此在有接口的情况下推荐优先使用JDK代理,没接口优先推荐使用Cglib代理技术。
 
动态代理与静态代理的比较
- 动态代理的最大优势就是在于可以把接口中声明的所有方法转移到调用处理器的一个集中的方法进行处理(
Invocation.invoke),在接口数量比较多的情况下,可以进行灵活处理。 - 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
 
相关文章:
都2024年了,还有人不懂动态代理么?
文章目录 一、定义二、静态代理三、动态代理1. JDK代理1.1 JDK代理实现流程1.2 动态生成的类字节码 2. Cglib代理2.1 Cglib实现流程 四、总结 一、定义 静态代理和动态代理都反映了一个代理模式,代理模式是一种经典的设计模式,常用于为其他对象提供一种…...
ARM功耗管理框架之PPU
安全之安全(security)博客目录导读 思考:功耗管理框架?SCP?PPU?LPI?之间的关系?如何配合? 目录 一、功耗管理框架中的PPU 二、PPU的结构与连接关系 三、PPU操作模式和电源模式及其之间的转…...
说说 SSL 的错误认识和不足之处
最近明月在学习折腾 LNMP 期间无意中建了一个 Typecho 的博客小站,近一周的折腾下来,收获真的不少,致使兴趣也越来越浓了,在升级 LNMP 的时候捎带手的给这个 Typecho 博客也启用了 SSL。并且开启了 memcached 和 OPcache 优化加速…...
Go语言day1
下载go语言的安装程序: All releases - The Go Programming Language 配置go语言的环境变量: 写第一个go语言 在E:\go_workspace当前窗口使用cmd命令: 输入 go run test.go...
【Python机器学习】利用t-SNE进行流形学习
虽然PCA通常是用于变换数据的首选方法,使你能够用散点图将其可视化,但这一方法的性质限制了其有效性。 有一类用于可视化的算法叫做流形学习算法,它允许进行更复杂的映射,通常也可以给出更好的可视化。其中特别有用的一个就是t-S…...
03 - matlab m_map地学绘图工具基础函数 - 设置坐标系(m_coord)
03 - matlab m_map地学绘图工具基础函数 - 设置坐标系(m_coord) 0. 引言1. m_proj使用方法2. 结语 0. 引言 上一篇介绍了m_proj函数用于初始化投影,本篇介绍的函数m_coord用于初始化地理坐标系或地磁坐标系,地理/地磁坐标系和投影…...
UEC++ 虚幻5第三人称射击游戏(一)
UEC 虚幻5第三人称射击游戏(一) 创建一个空白的C工程 人物角色基本移动 创建一个Character类添加一些虚幻商城中的基础动画 给角色类添加Camera与SPringArm组件 UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category "SpringArm")clas…...
java小代码(1)
代码 : 今日总结到此结束,拜拜!...
SLAM ORB-SLAM2(27)词袋模型
SLAM ORB-SLAM2(27)词袋模型 1. 词袋模型1.1. 词汇树1.2. 逆向索引表1.3. 逆向索引表2. 词袋向量3. 匹配候选帧3.1. 找出和当前帧具有公共单词的所有关键帧3.2. 找出和当前帧最多公共单词的关键帧3.3. 剔除共享单词数较少的关键帧3.4. 计算关键帧的共视关键帧组的总得分3.5. …...
OpenAI 的 GPT-5:CTO米拉-穆拉提说,到 2026 年将实现博士级智能(Ph.D.-Level))
据首席技术官米拉-穆拉提(Mira Murati)介绍,GPT-5 是 OpenAI 人工智能的下一代进化产品,将于 2025 年底或 2026 年初在特定任务中实现博士级智能。 GPT-5 内部代号为 "Gobi "和 “Arrakis”,将是一个多模态…...
macbook配置adb环境和用adb操作安卓手机
(参考:ADB工具包的安装与使用_adb工具箱-CSDN博客) 第一步:从Android开发者网站下载Android SDK(软件开发工具包)。下载地址为: 第二步:解压下载的SDK压缩文件到某个目录中。 进入解…...
微软TTS最新模型,发布9种更真实的AI语音
很高兴与大家分享 Azure AI 语音翻译产品套件的两个重大更新: 视频翻译和增强的实时语音翻译 API。 视频翻译(批量) 今天,我们宣布推出视频翻译预览版,这是一项突破性的服务,旨在改变企业本地化视频内容…...
python爬虫 -爬取 json 格式数据
在Python中,爬取JSON格式的数据通常涉及到发送 HTTP请求到某个URL,并解析返回的JSON数据。以下是一个简单的示例,说明如何使用Python的requests库来爬取JSON格式的数据: 1. 首先,确保你已经安装了requests库。如果没…...
Pytorch(5)-----梯度计算
一、问题 如何使用Pytorch计算样本张量的基本梯度呢?考虑一个样本数据集,且有两个展示变量,在给定初始权重的基础上,如何在每次迭代中计算梯度呢? 二、如何运行 假设有x_data 和 y_data 列表,计算两个列表需…...
C#的膨胀之路:创新还是灭亡
开篇概述 C#,这门由微软推出的编程语言,自2000年诞生以来,以其简洁的语法、强大的功能和广泛的应用场景,赢得了我等程序员的热爱。它在.NET框架的加持下,展现出无与伦比的开发效率和性能。然而,随着时间的流…...
SpringBoot 过滤器和拦截器的区别
SpringBoot 过滤器和拦截器的区别 Spring拦截器(Interceptor)和过滤器(Filter)是Spring框架中用于处理请求的两种机制,虽然它们都可以在请求处理的不同阶段进行拦截和处理,但它们的工作原理和应用场景有所…...
协程执行顺序引发的问题
引言 在Golang中,因为协程执行的顺序是不固定的,如果不在代码里进行控制,可能就会导致预期外的输出。 本文通过分析一段代码的执行来介绍这种情况,以及可行的控制协程执行顺序的方法: sleep()waitGroup 实例分析 代…...
android webview调用js滚动到指定位置
一、activity import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.tencent.smtt.sdk.WebView import com.tencent.smtt.sdk.WebViewClientclass MainActivity : AppCompatActivity() {private lateinit var webView: WebViewoverride fun …...
WPF 深入理解一、基础知识介绍
基础知识 本系列文章是对个人 B站 up 微软系列技术教程 记录 视频地址 https://www.bilibili.com/video/BV1HC4y1b76v/?spm_id_from333.999.0.0&vd_source0748f94a553c71a2b0125078697617e3 winform 与 wpf 异同 1.winform 项目结构 编辑主要是在 Form1.cs(页面)&#…...
腾讯云点播ugc upload | lack signature 问题处理
我犯一个很傻的错误 参考腾讯云官方文档:云点播 Web 端上传 SDK-开发指南-文档中心-腾讯云 进行开发,但是却报错了,始终找不到问题,错误提示:ugc upload | lack signature,意思是缺少签名或者签名失败&…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
Mac flutter环境搭建
一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…...
