当前位置: 首页 > news >正文

JDK动态代理详解

1.什么是动态代理

代理

可能很多小伙伴首次接触动态代理这个名词的时候,或者是在面试过程中被问到动态代理的时候,不能很好的描述出来,动态代理到底是个什么高大上的技术。不方,其实动态代理的使用非常广泛,例如我们平常使用的Spring中的@Transactional注解,其依赖于AOP,而AOP的底层实现便是动态代理,看到这里,是不是更有兴趣去了解动态代理了呢?

动态代理:可以分解为“动态”+“代理”。

  • 代理:“代理”一词,在我们的生活中也是随处可见的,例如房屋中介,其是对房主的一种代理,房主需要出租其房屋,但是可能没时间去接待租客,给租客介绍房屋信息,带领租客看房,但是房屋中介可以为租客提供这些服务,所以,代理其是对被代理对象的一个功能增强
  • 动态:“动态”通常与“静态”相比较,“静态”描述的是事物是固定存在的,“动态”则描述的是事物是随着需求而动态生成的。

所以,静态代理存在一定的局限性,不能很好的满足需求的千变万化,动态代理的出现,就是为了解决这些局限性。

我们先来看看静态代理。

2.静态代理

在开发中,通常需要为方法添加日志打印,能够记录程序的执行过程,以便后续出现异常问题的时候,能更好的排查定位。

假设我们现在已经完成了系统用户的增加、删除、修改等功能,这些功能在类UserServiceImpl中已经实现。

代码示例:

public class UserServiceImpl {public void add() {System.out.println("添加用户");}public void update() {System.out.println("修改用户");}public void delete() {System.out.println("删除用户");}
}

现在,我们需要在UserServiceImpl类中的方法添加日志功能,那么怎么才能更好地去实现这个需求呢?

1)直接在目标方法前后添加日志代码

代码示例:

public class UserServiceImpl {public void add() {System.out.println("====== add方法开始 ======");System.out.println("添加用户");System.out.println("====== add方法结束 ======");}public void update() {System.out.println("====== update方法开始 ======");System.out.println("修改用户");System.out.println("====== update方法结束 ======");}public void delete() {System.out.println("====== delete方法开始 ======");System.out.println("删除用户");System.out.println("====== delete方法结束 ======");}
}

观察上述代码,这种方式的缺点在于:

  • 如果UserServiceImpl类中,有很多的方法,修改量大,且存在大量重复代码,不利于后期维护。
  • 直接修改源代码,不符号开闭原则。应该对扩展开放,对修改关闭。

2)静态代理方式实现

静态代理需要我们将目标类的方法抽取到接口中,代理类和目标类实现同一个接口,既然要实现代理,代理类自然需要在其内部维护目标对象的引用,并通过构造函数为其赋值,然后在代理类的方法中调用目标对象的同名方法,并在调用前后完成功能的增强。

实现步骤:

  • 抽取UserService接口
  • 创建目标类UserServiceImpl实现UserService接口
  • 创建代理类UserServiceProxy实现UserService接口
  • 代理类中完成功能的增强

代码实现:

// 目标接口
public interface UserService {void add();void update();void delete();
}
// 目标类
public class UserServiceImpl implements UserService {@Overridepublic void add() {System.out.println("添加用户");}@Overridepublic void update() {System.out.println("修改用户");}@Overridepublic void delete() {System.out.println("删除用户");}
}
// 代理类
public class UserServiceProxy implements UserService {private UserService userService;public UserServiceProxy(UserService userService) {this.userService = userService;}@Overridepublic void add() {System.out.println("====== add方法开始 ======");userService.add();System.out.println("====== add方法结束 ======");}@Overridepublic void update() {System.out.println("====== update方法开始 ======");userService.update();System.out.println("====== update方法结束 ======");}@Overridepublic void delete() {System.out.println("====== delete方法开始 ======");userService.delete();System.out.println("====== delete方法结束 ======");}
}

观察上述代码,静态代理遵循开闭原则,在不修改目标类的前提下,完成了功能的增强,但是依然存在大量重复的代码,且一个代理类只能代理一个目标类,如果有n个目标类需要被代理,就需要同比增加n个代理类。

那么,有没有办法可以使得我们不需要去定义这么多的代理类,就可以实现对目标类功能的增强?答案是有的:动态代理

2.JDK动态代理

前面,我们提到静态代理的实现方式:代理类和目标类都实现同一个接口,在代理类中维护目标类对象,并完成对目标类对象方法的增强,这种方式虽然遵循开闭原则,但是代理类和目标类至少是“一对一”的绑定关系,如果需要被代理的目标类个数越多,代理类就会越多,会产生大量重复的代码,也不利于后期的维护。

从静态代理中,我们知道代理类也是接口的一个实现类,代理对象的类型也是属于接口类型,我们来验证一下。

public class Test {public static void main(String[] args) {UserServiceProxy userServiceProxy = new UserServiceProxy(new UserServiceImpl());System.out.println(userServiceProxy instanceof UserService);}
}
// 打印结果:true

那么,能不能动态生成这些代理对象呢?我们知道类是构造对象的模板,代理类都还不存在,怎么去构造代理对象呢?

除了不存在代理类,还剩下UserService接口和UserServiceImpl目标类,JDK动态代理的目的就是通过接口来生成代理类以及代理类的对象,我们知道接口是不能直接通过new关键字创建对象的。

那么JDK动态代理是怎么创建出代理类以及代理类对象的呢?

我们先来看看通过new关键字创建对象的过程。

UserServiceImpl userService = new UserServiceImpl();
/*
创建对象的过程:1.执行new指令,如果类未加载,先执行类加载过程。1.加载:JVM通过ClassLoader将UserServiceImpl.class文件加载到方法区(Method Area),在堆内存中创建代表该类的Class对象。2.验证3.准备:为静态变量分配内存并设置类型初始值。4.解析5.初始化:为静态变量赋值、执行静态代码块2.为对象分配内存,将对象的实例字段初始化类型零值。3.执行构造方法,对对象进行初始化
*/

对象创建过程

追踪上述过程,我们得知创建对象,需要先得到该类的Class对象,通过Class对象去创建实例对象。为了验证这一点,我们不妨来看看通过反射的方式创建对象的过程。

public class Test {@SneakyThrowspublic static void main(String[] args) {// 获取Class对象Class<UserServiceImpl> userServiceClass = UserServiceImpl.class;// 获取构造器Constructor<?>[] constructors = userServiceClass.getConstructors();for (Constructor<?> constructor : constructors) {// 通过构造器创建实例对象System.out.println(constructor.newInstance());}}
}

现在,问题回归到接口不能直接new,也没有构造方法,并且不存在代理类的class文件,怎么获得Class对象了。

动态代理关键类

我们先来看看JDK动态代理的实战代码:

  • 需要自定义个CustomInvocationHandler实现InvocationHandler接口。
  • 利用Proxy.newProxyInstance构建实例对象。
// UserService接口
public interface UserService {void add();void update();void delete();
}// 目标类
public class UserServiceImpl implements UserService {@Overridepublic void add() {System.out.println("添加用户");}@Overridepublic void update() {System.out.println("修改用户");}@Overridepublic void delete() {System.out.println("删除用户");}
}// CustomInvocationHandler
public class CustomInvocationHandler implements InvocationHandler {// 目标对象private Object target;public CustomInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("====== 方法开始 ======");Object result = method.invoke(target, args);System.out.println("====== 方法结束 ======");return result;}
}public class Test {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();// 关键代码UserService service = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new CustomInvocationHandler(userService));service.add();}
}

从测试代码可以看出,Proxy类是关键。我们来看看Proxy为我们提供的方法:

Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)虽然被标注为过时方法,但是从名字上可以得知,其目的是为了获得代理类的Class对象。话不多说,我们来调用一下。

public class Test {public static void main(String[] args) {Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());System.out.println(proxyClass.getName());for (Method method : proxyClass.getDeclaredMethods()) {System.out.println(method.getDeclaringClass() + "." + method.getName() + "()");}System.out.println(Arrays.toString(proxyClass.getConstructors()));}
}

可以看到:

  • 获得的Class对象的名称为$Proxy0
  • 定义了我们需要的add()、update()、delete()方法。
  • 定义了一个有参构造方法$Proxy0(InvocationHandler handler)

虽然没有无参构造方法,我们还是得尝试一下调用一下这个有参的构造方法,需要我们传入一个java.lang.reflect.InvocationHandler对象

public class Test {@SneakyThrowspublic static void main(String[] args) {System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());// 获取$Proxy0(InvocationHandler handler)构造方法Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);UserService userService = (UserService) constructor.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(proxy.getClass());System.out.println(method.getDeclaringClass() + "." + method.getName() + "()");return null;}});userService.add();}
}

看的出来,**当我们获得代理对象之后,通过代理对象来调用接口方法,都会回调构造时传进来的InvocationHandler对象的invoke(Object proxy, Method method, Object[] args)方法,**该方法有3个参数:

  • Object proxy:代表的是代理对象本身。
  • Method method:代表的是被调用的方法的Method对象。
  • Object[] args:代表的是被调用方法的参数。

可以猜测,JDK动态代理生成的代理类中,维护了InvocationHandler类的对象变量,并且在实现接口方法时,通过InvocationHandler对象调用了invoke(Object proxy, Method method, Object[] args)方法。

System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

不知道大家没有看到这行代码哈,当添加了这行代码之后,可以将在项目目录下保存动态创建的class文件,com/sun/proxy/$Proxy0.class

可以看到生成的代理类$Proxy0继承自Proxy类,并实现了UserService接口,并且在add()方法中通过其父类Proxy中维护的InvocationHandler对象调用invoke()方法,这也就成功的解释了前面调用userService.add()方法,会回调到invoke()方法。

这时候我们再把代码改造一下,如下:

public class CustomInvocationHandler implements InvocationHandler {// 目标对象private Object target;public CustomInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("====== 方法开始 ======");Object result = method.invoke(target, args);System.out.println("====== 方法结束 ======");return result;}
}public class Test {@SneakyThrowspublic static void main(String[] args) {UserServiceImpl target = new UserServiceImpl();Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);UserService userService = (UserService) constructor.newInstance(new CustomInvocationHandler(target));userService.add();}
}

这样就完成了对目标对象功能的增强,前面我们提到过Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

已经被标注为过时,推荐我们使用Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法。

动态代理设计思想

好的,到这里,我们来总结一下JDK动态的设计思想:

使用JDK动态代理,使得我们免去编写代理类,只需要将增强功能编写在InvocationHandlerinvoke方法中。

相关文章:

JDK动态代理详解

1.什么是动态代理 可能很多小伙伴首次接触动态代理这个名词的时候&#xff0c;或者是在面试过程中被问到动态代理的时候&#xff0c;不能很好的描述出来&#xff0c;动态代理到底是个什么高大上的技术。不方&#xff0c;其实动态代理的使用非常广泛&#xff0c;例如我们平常使用…...

实时的软件生成 —— Prompt 编程打通低代码的最后一公里?

PS&#xff1a;这也是一篇畅想&#xff0c;虽然经过了一番试验&#xff0c;依旧有一些不足&#xff0c;但是大体上站得住脚。传统的软件生成方式需要程序员编写大量的代码&#xff0c;然后进行测试、发布等一系列繁琐的流程。而实时生成技术则是借助人工智能技术&#xff0c;让…...

互联网工程师 1480 道 Java 面试题及答案整理 ( 2023 年 整理版)

最近很多粉丝朋友私信我说&#xff1a;熬过了去年的寒冬却没熬过现在的内卷&#xff1b;打开 Boss 直拒一排已读不回&#xff0c;回的基本都是外包&#xff0c;薪资还给的不高&#xff0c;对技术水平要求也远超从前&#xff1b;感觉 Java 一个初中级岗位有上千人同时竞争&#…...

Spark开发

第一步&#xff1a;创建RDD Spark提供三种创建RDD方式&#xff1a;** 集合、本地文件、HDFS文件** 使用程序中的集合创建RDD&#xff0c;主要用于进行测试&#xff0c;可以在实际部署到集群运行之前&#xff0c;自己使用集合构造一些测试数据&#xff0c;来测试后面的spark应…...

Tornado异步框架

简介&#xff1a; tornado是Python的web框架。tornado和主流的web服务器框架有明显的区别&#xff1a;它是非阻塞式服务器&#xff0c;而且速度非常快&#xff0c;得力于其非阻塞的方式和epoll的运用tornado可以每秒处理数以千计的连接&#xff08;号称&#xff09; 基本配置 …...

openpnp - error - 吸嘴没下降到板子上, 就将元件松开

文章目录openpnp - error - 吸嘴没下降到板子上, 就将元件松开概述笔记ENDopenpnp - error - 吸嘴没下降到板子上, 就将元件松开 概述 以前用过国内一家openpnp厂家出的设备, 他们家的openpnp是自己改过的. 贴片流程已经走过一遍. 这次还是按照以前记录的笔记, 按照国内那家的…...

【Java】yyyy-MM-dd HH:mm:ss 时间格式 时间戳 全面解读超详细

时间格式 时间格式(协议)描述gg时期或纪元。y不包含纪元的年份。不具有前导零。yy不包含纪元的年份。具有前导零。yyyy包含纪元的四位数的年份。M月份数字。一位数的月份没有前导零。MM月份数字。一位数的月份有一个前导零。MMM月份的缩写名称&#xff0c;在AbbreviatedMonthN…...

快鲸SCRM发布口腔企业私域运营解决方案

口腔企业普遍面临着以下几方面运营痛点问题 1、获客成本居高不下&#xff0c;恶性竞争严重 2、管理系统落后&#xff0c;人员流失严重 3、客户顾虑多、决策时间长 4、老客户易流失&#xff0c;粘性差 以上这些痛点&#xff0c;不得不倒逼口腔企业向精细化运营客户迈进。 …...

Verilog实现组合逻辑电路

在verilog 中可以实现的数字电路主要分为两类----组合逻辑电路和时序逻辑电路。组合逻辑电路比较简单&#xff0c;仅由基本逻辑门组成---如与门、或门和非门等。当电路的输入发生变化时&#xff0c;输出几乎&#xff08;信号在电路中传递时会有一小段延迟&#xff09;立即就发生…...

2023前端菜鸟笔试血泪史html5-one--找到工作前都更新

1.说说对html语义化的理解 什么的HTML语义化&#xff0c;顾名思义&#xff0c;HTML语义化就是可以不通过了解HTML的内容&#xff0c;就可以知道这个部分所代表的的意义。 HTML语义化的意义&#xff1a;在使用HTML标签构建页面时&#xff0c;避免大篇幅的使用无语义的标签。 …...

蓝牙调试工具集合汇总

BLE 该部分主要分享一下常用的蓝牙调试工具&#xff0c;方便后续蓝牙抓包及分析。 目录 1 hciconfig 2 hcitool 3 hcidump 4 hciattach 5 btmon 6 bluetoothd 7 bluetoothctl 1 hciconfig 工具介绍&#xff1a;hciconfig&#xff0c;HCI 设备配置工具 命令格式&…...

Java 获取文件后缀名【一文总结所有方法】

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

UML常见图的总结

一、概述 UML&#xff1a;Unified Modeling Language&#xff0c;统一建模语言&#xff0c;支持从需求分析开始的软件开发的全过程。是一个支持模型化和软件系统开发的图形化语言、为软件开发的所有阶段提供模型化和可视化支持&#xff0c;包括由需求分析到规格&#xff0c;到…...

WebRTC系列-工具系列之音频相关工具

文章目录 1. audio_util数据格式转换类2. WavFile文件读写类2.1 读取wav文件2.2 写入wav文件这篇文章主要介绍WebRTC中一些音频工具这些,大部分都在 common_audio目录下,这个文件夹下提供音频的大量算法,包括sinc重采样算法,音频数据格式的转换:例如 float转int16_t格式等…...

7 线性回归及Python实现

1 统计指标 随机变量XXX的理论平均值称为期望: μE(X)\mu E(X)μE(X)但现实中通常不知道μ\muμ, 因此使用已知样本来获取均值 X‾1n∑i1nXi.\overline{X} \frac{1}{n} \sum_{i 1}^n X_i. Xn1​i1∑n​Xi​.方差variance定义为&#xff1a; σ2E(∣X−μ∣2).\sigma^2 E(|…...

适合小团队协作、任务管理、计划和进度跟踪的项目任务管理工具有哪些?

适合小团队协作、任务管理、计划和进度跟踪的项目任务管理工具有哪些? 大家可以参考这个模板&#xff1a;http://s.fanruan.com/irhj8管理项目归根结底在管理人、物&#xff0c;扩展来说便是&#xff1a; 人&#xff1a;员工能力、组织机制&#xff1b; 物&#xff1a;项目内…...

从100%进口到自主可控,从600块降到10块,中科院攻克重要芯片

前言 2月28日&#xff0c;“20多位中科院专家把芯片价格打到10块”冲上微博热搜&#xff0c;据河南省官媒大象新闻报道&#xff0c;热搜中提到的中科院专家所在企业为全球最大的PLC分路器芯片制造商仕佳光子&#xff0c;坐落于河南鹤壁。 为实现芯片技术自主可控自立自强&#…...

关于git的一些基本点总结

1.什么是git? git是一个常用的分布式版本管理工具。 2.git 的常用命令: clone&#xff08;克隆&#xff09;: 从远程仓库中克隆代码到本地仓库 checkout &#xff08;检出&#xff09;:从本地仓库中检出一个仓库分支然后进行修订 add&#xff08;添加&#xff09;: 在提交前…...

PyTorch保姆级安装教程

1 安装CUDA1.1 查找Nvidia适用的CUDA版本桌面右键&#xff0c;【打开 NVIDIA控制面板】查看【系统信息】查看NVIDIA的支持的CUDA的版本&#xff0c;下图可知支持的版本是 10.11.2 下载CUDACUDA下载官方网址https://developer.nvidia.com/cuda-toolkit-archive找到适合的版本下载…...

MySQL 上亿大表如何优化?

背景XX 实例&#xff08;一主一从&#xff09;xxx 告警中每天凌晨在报 SLA 报警&#xff0c;该报警的意思是存在一定的主从延迟。&#xff08;若在此时发生主从切换&#xff0c;需要长时间才可以完成切换&#xff0c;要追延迟来保证主从数据的一致性&#xff09;XX 实例的慢查询…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

Razor编程中@Html的方法使用大全

文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

全面解析数据库:从基础概念到前沿应用​

在数字化时代&#xff0c;数据已成为企业和社会发展的核心资产&#xff0c;而数据库作为存储、管理和处理数据的关键工具&#xff0c;在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理&#xff0c;到社交网络的用户数据存储&#xff0c;再到金融行业的交易记录处理&a…...

Linux安全加固:从攻防视角构建系统免疫

Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...