Java从入门到精通(十二)~ 动态代理

 晚上好,愿这深深的夜色给你带来安宁,让温馨的夜晚抚平你一天的疲惫,美好的梦想在这个寂静的夜晚悄悄成长。

文章目录

目录

前言

主要作用和功能:

应用场景:

二、代理概念

1.静态代理

2.动态代理

2.1 概念介绍

代理对象真实的样子

2.2 代码实现

接口规范:

 真实对象

 代理对象:

测试类: 

测试结果:​编辑 

2.3.  流程分析 

1.main方法中调用createProxy(new ArrayList())将真实对象传给代理工具类进行代理。

2.代理工具类接收到真实对象,会调用newProxyInstance()通过反射创建对应的代理对象。

3. 底层根据三个参数创建完,代理对象后,会将其返回。

4. 执行listProxy.containsAll(targetList); 会跳转到代理对象,通过透传调用InvocationHandler接口的invoke()方法,并将接口反射到对应的containsAll()方法的信息传递传递过去。


前言

无反射无Java,无动态代理无框架。

  1. 动态代理可以帮助在不修改原始类代码的情况下,对原始类的方法进行增强、添加额外的处理逻辑或者拦截某些操作。 
  2. 动态代理实现的关键在于利用反射动态生成代理类或者代理对象,这些代理对象可以拦截对真实对象方法的调用,并在调用前后执行额外的逻辑。
  3. 动态代理是一种在运行时动态生成代理类的技术,它允许在不事先创建实际实现类的情况下,创建一个实现了特定接口或一组接口的代理类。


一、什么是类加载器?

类加载器(Class Loader)在Java中是一个重要的运行时系统组件,负责将字节码文件加载到内存中,并生成对应的类对象。在Java虚拟机(JVM)中,每个类都需要在运行时动态加载到内存中才能被使用,而类加载器就是完成这个任务的核心。

主要作用和功能:

  1. 加载类文件: 类加载器负责从文件系统、JAR文件、网络中加载.class文件,并将其转换为一个Class对象。

  2. 类的唯一性: 类加载器确保每个类只被加载一次,即使多次请求加载同一个类,也只会得到同一个Class对象的引用。

  3. 双亲委派模型: Java的类加载器采用双亲委派模型(Parent Delegation Model),即除了顶层的启动类加载器外,其余类加载器都有父类加载器。当一个类加载器收到类加载请求时,它会先委托给父类加载器进行加载,只有在父类加载器无法加载时,才会自己尝试加载。

  4. 安全性: 类加载器也参与了Java安全模型的实现,例如沙箱安全性。

应用场景:

类加载器的灵活性使得Java具有了丰富的动态性和扩展性,例如在Web容器中,每个Web应用程序都有独立的类加载器实例,可以隔离不同应用程序的类加载,避免冲突。在框架和插件化系统中,类加载器的使用也十分普遍,可以动态加载和卸载模块。

二、代理概念

在代理模式中,有三个主要角色:

  • 抽象对象(Subject):定义了真实对象和代理对象的公共接口,客户端通过这个接口访问真实对象或代理对象。
  • 真实对象(Real Subject):实现了抽象角色定义的具体业务逻辑,是代理模式中被代理的对象。
  • 代理对象(Proxy):持有对真实对象的引用,并实现了抽象角色定义的接口,可以在调用真实对象之前或之后执行额外的操作。

1.静态代理

继承实际上就是静态代理,通过父类应用调用子类重写的方法,子类对父类进行了代理,这种代理是在运行前就生成的字节码,但这种存在问题,真实代码如果进行增加删除修改,子类也需要,维护成本就比较高。

// 定义接口 Person
interface Person {void doWork();
}// 实现接口的学生类
class Student implements Person {@Overridepublic void doWork() {System.out.println("学习");}
}// 静态代理类实现接口 Person
class StaticProxy implements Person {private Person person;public StaticProxy(Person person) {this.person = person;}@Overridepublic void doWork() {System.out.println("doSomething ----- start");person.doWork();System.out.println("doSomething ----- end");}
}// 测试类
public class ProxyDemo {public static void main(String[] args) {// 创建一个学生对象Person student = new Student();// 创建静态代理对象,将学生对象作为参数传入StaticProxy staticProxy = new StaticProxy(student);// 调用静态代理对象的 doWork 方法staticProxy.doWork();}
}

其实静态代理也是很繁琐的,我们需要为每一个类,每一个方法添加相应的操作。 

上面的代码我们不难发现 静态代理 存在一个问题,代理主题类与真实主题类之间的 耦合程度太高 ,当真实主题类中增加、删除、修改方法后,那么代理主题类中的也必须增加、删除、修改相应的方法,提高了代码的维护成本。另一个问题就是当代理对象代理多个 Subject 的实现类时,多个实现类中必然出在不同的方法,由于代理对象要实现与目标对象一致的接口(其实是包含关系),必然需要编写众多的方法,极其容易造成臃肿且难以维护的代码。

2.动态代理

2.1 概念介绍

动态代理是在运行时动态生成的类或者对象,而不是编译时静态确定的类。这与传统的继承关系有所不同,传统继承是在编译时确定的静态关系。

  1. 代理类:代理对象和真实对象实现同一个接口,其中代理对象包含真实对象的所有方法,然后对真实对象的所有方法的调用的时候,通通先去调用代理类的Invocation handler。所有方法都走我的代理类,那我就可以在我的代理类去做任何我想做的逻辑了。
  2. Invocation handler是一个增强器,proxy是一个调度器:你去找的时候总会找一个为你服务。
  3. 那其实这是最核心的呢就是通过我们的Porxy.newProxyInstance方法去生成我们的动态代理类以及访问它的实例。 

当客户端调用代理对象的方法时,实际上是调用了 InvocationHandler 的 invoke 方法,传递了代理对象、方法和参数等信息。当代理对象调用任何方法时,都会回调这个方法,在动态代理模式中,代理对象会在运行时将方法调用转发给实现了InvocationHandler接口的处理器对象。代理对象在调用方法时,实际上是在"回调"(调用)InvocationHandler接口的方法,将控制权交给处理器对象。时机:在调用被代理对象的方法的时候,触发:会自动回调invoke()方法。

代理对象真实的样子

设置生成的动态代理的代码生成到指定路径,方便我们查看。

其中super.h:表示父类Proxy 类的成员变量InvocationHandler在newProxyInstance创建代理对象的时候,将参数三匿名内部类的地址值传递给h,因此调用super.h也就是你传的参数,然后将m3通过反射获取的接口中对应类的save()方法的信息。

2.2 代码实现

接口规范:
package proxy;/*** @author windStop* @version 1.0* @description user业务层* @date 2024年07月27日23:37:55*/
public interface UserService {boolean login(String name,String password) throws InterruptedException;String selectUsers(int id) throws InterruptedException;boolean deleteUsers(int id) throws InterruptedException;
}
 真实对象
package proxy;/*** @author windStop* @version 1.0* @description User业务层实现类* @date 2024年07月27日23:40:01*/
public class UserServiceImpl implements UserService{@Overridepublic boolean login(String name, String password) throws InterruptedException {boolean flag = false;if (name.equals("windStop") && password.equals("123456")){flag = true;}Thread.sleep(3000);return flag;}@Overridepublic String selectUsers(int id) throws InterruptedException {System.out.println("查询成功");Thread.sleep(200);return "查询成功";}@Overridepublic boolean deleteUsers(int id) throws InterruptedException {System.out.println("删除成功");Thread.sleep(200);return true;}
}
 代理对象:
package proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author windStop* @version 1.0* @description 代理工具类* @date 2024年07月27日23:37:01*/
public class ProxyUtil {//ClassLoader: 用于定义代理类的类加载器。动态代理需要在运行时生成代理类的字节码,//因此需要指定一个类加载器来加载这些类。通常使用当前类的类加载器来加载。public static UserService createProxy(UserService userService){UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),new Class[]{UserService.class},//将方法调用分派给的调用处理程序new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("login") || method.getName().equals("selectUsers")|| method.getName().equals("deleteUsers")){long startTime = System.currentTimeMillis();Object rs = method.invoke(userService, args);long endTime = System.currentTimeMillis();System.out.println(method.getName() + "方法执行了" + (endTime - startTime) + "ms");return rs;}else{return method.invoke(userService,args);}}});return userServiceProxy;}
}
测试类: 
package proxy;/*** @author windStop* @version 1.0* @description 测试动态代理* @date 2024年07月27日23:31:58*/
public class Test1 {public static void main(String[] args) throws InterruptedException {UserService userServiceProxy = ProxyUtil.createProxy(new UserServiceImpl());userServiceProxy.deleteUsers(10);userServiceProxy.selectUsers(20);userServiceProxy.login("xiaoming","123");}
}
测试结果:

2.3.  流程分析 

List listProxy = ProxyUtil.createProxy(new ArrayList<>());
List<String> targetList = new ArrayList<>();
targetList.add("item1");
targetList.add("item2");
listProxy.containsAll(targetList);

通过上述代码,我们可以看出:我们并没有在真实对象中进行时间计算,但结果会给我返回该流程所执行的时间,这就是动态代理的核心作用,在没有惊动我真实对象的代码前提下,动态的给其进行添加功能。

1.main方法中调用createProxy(new ArrayList())将真实对象传给代理工具类进行代理。
2.代理工具类接收到真实对象,会调用newProxyInstance()通过反射创建对应的代理对象。

参数一:用于定义代理类的类加载器。这里需要类加载器的原因是: 

动态代理需要显式提供类加载器的原因是因为它们生成的代理类不是预先存在的,而是在运行时动态生成的。为了确保这些动态生成的代理类能够被正确加载和使用,需要通过 Proxy.newProxyInstance 方法显式指定一个合适的类加载器来加载这些类。通常使用当前类的类加载器来加载。

参数二:提供定义规范的接口。

为什么要定义抽象对象(接口)呢?

  1. 类型匹配和约束

    • 接口定义了被代理对象和代理对象必须遵循的契约。动态代理根据接口来生成代理类,确保代理对象可以替代真实对象。这种约束保证了代理对象可以正确地提供和被代理对象相同的方法和行为。
  2. 实现多态

    • 接口使得代理对象在运行时可以动态替代被代理对象,实现了多态性。客户端代码通过统一的接口调用代理对象和被代理对象的方法,而不需要关心具体是哪个对象在处理请求。这种特性使得代码更加灵活和可扩展。
  3. 动态生成代理类

    • 动态代理技术通常在运行时生成代理类,这与静态代理不同,静态代理是在编译期间就已经确定了代理类的实现。基于接口的代理可以根据接口定义灵活地生成代理对象的代码,这种动态生成的能力使得代理对象的行为可以在运行时根据需要进行适配和修改。
  4. 解耦合

    • 接口定义了被代理对象的行为和代理对象提供的功能,从而实现了解耦合。代理对象可以在不影响客户端代码的情况下替换被代理对象,这种灵活性和解耦合的设计符合面向对象设计的依赖倒置原则和单一职责原则。客户端只需要依赖于接口,而不需要关心具体的实现类,从而提高了代码的灵活性和可维护性。
  5. 面向接口编程:可以利用动态代理,获取出接口的方法,然后通过代理对象调用该方法,就是多态的情景了,子类对象调用父类方法(调用的实际是被虚方法表覆盖的方法)。如果代理对象没有继承该接口,就无法调用接口中反射的方法,就会抛出IllegalAccessException 异常。

总之,接口在动态代理中的应用,不仅限于定义契约和约束,还包括实现多态、动态生成代理类以及实现解耦合等重要作用,这些特性使得动态代理在实际应用中具有广泛的适用性和灵活性。

 参数三:调用处理器,当用户使用代理对象调用了方法后,通通先去调用我的InvocationHandler去做任何我想做的逻辑了 。

3. 底层根据三个参数创建完,代理对象后,会将其返回。

创建出对象的样子:

继承抽取出的接口规范接口,生成该接口的所有方法的属性。

通过反射对这些(方法类型)的成员变量赋值。

4. 执行listProxy.containsAll(targetList); 会跳转到代理对象,通过透传调用InvocationHandler接口的invoke()方法,并将接口反射到对应的containsAll()方法的信息传递过去。

注意:其中super.h:表示父类Proxy 类的成员变量InvocationHandler

最后执行invoke()代码的逻辑

  1. proxy:用来调用代理对象自身的其他方法;
  2. method:用来获取被调用方法的详细信息;
  3. args:则是被调用方法的具体参数值。
  4. 返回值:invoke 方法的返回值是被代理方法的返回值。这里返回的对象是实际被代理方法执行后的结果。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网址导航系统PHP源码分享

1、采用光年全新v5模板开发后台 2、后台内置8款主题色&#xff0c;分别是简约白、炫光绿、渐变紫、活力橙、少女粉、少女紫、科幻蓝、护眼黑 3、可管理无数引导页主题并且主题内可以进行不同的自定义设置&#xff0c;目前内置16套主题 持续增加中… 4、可单独开发各种插件&a…

OSPF Type2 Message / DBD Packet (Database Descriptor)

注&#xff1a;机翻&#xff0c;未校对。 OSPF Type2 Message / DBD Packet (Database Descriptor) DBD (Database Description or Type2 OSPF Packet) is a sort of summary of the OSPF Database in a router. DBD is used to check if the LSDB between 2 routers is the s…

Linux---make/makefile工具

目录 基本了解 makefile基础语法 依赖关系 依赖方法 makefile文件内容格式 make执行机制 补充知识 机制解释 PHONY关键字 makefile补充语法 基本了解 在Linux中&#xff0c;make/makefile是项目自动化构建工具。如果我们没有make/makefile&#xff0c;那我们要编译一…

基于Java的模拟写字板的设计与实现

点击下载链接 基于Java的模拟写字板的设计与实现 摘要&#xff1a;目前&#xff0c;很多新的技术领域都涉及到了Java语言&#xff0c;Java语言是面向对象编程&#xff0c;并且涉及到网络、多线程等重要的基础知识&#xff0c;因此Java语言也是学习面向对象编程和网络编程的首…

Linux系统编程——生产者消费者模型

目录 一&#xff0c;模型介绍 1.1 预备知识&#xff08;超市买东西的例子&#xff09; 1.2 模型介绍 1.3 CP模型特点 二&#xff0c;基于阻塞队列的CP模型 2.1 介绍 2.2 阻塞队列的实现 2.3 主函数实现 2.4 效果展示 三&#xff0c;POSIX信号量 3.1 信号量原理 3…

力扣 快慢指针

1 环形链表 141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; 定义两个指针&#xff0c;一快一慢。慢指针每次只移动一步&#xff0c;而快指针每次移动两步。初始时&#xff0c;慢指针和快指针都在位置 head&#xff0c;这样一来&#xff0c;如果在移动的过程中&#x…

05。拿捏ArkTS 第 3 天 --- 对象、联合类型、枚举

1&#xff0c;什么是对象&#xff1f;对象是干什么的&#xff1f; &#xff5e;用来存储不同类型数据的容器 &#xff5e;用来描述物体的特征和行为 //特征就是属性&#xff0c;行为就是方法&#xff08;对象内的函数&#xff09; 2&#xff0c;对象的基本样式是&#xff1f; …

Noah-MP陆面生态水文模拟与多源遥感数据同化技术

了解陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用&#xff1b;熟悉模型的发展历程&#xff0c;常见模型及各自特点&#xff1b;理解Noah-MP模型的原理&#xff0c;掌握Noah-MP模型在单站和区域的模拟、模拟结果的输出和后续分析及可视化等方法&#xff1b;…

OpenGL入门第六步:材质

目录 结果显示 材质介绍 函数解析 具体代码 结果显示 材质介绍 当描述一个表面时&#xff0c;我们可以分别为三个光照分量定义一个材质颜色(Material Color)&#xff1a;环境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)和镜面光照(Specular Lighting)。通过为每…

23.jdk源码阅读之Thread(下)

1. 写在前面 上篇文章我们介绍了Tread的一些方法的底层代码实现&#xff0c;这篇文章我们继续。 2. join()方法的底层实现 /*** Waits at most {code millis} milliseconds for this thread to* die. A timeout of {code 0} means to wait forever.** <p> This impleme…

从工艺到性能:模具3D打印材料不断革新

在模具3D打印领域&#xff0c;材料性能的持续优化与创新是推动模具3D打印的关键因素&#xff0c;近年来&#xff0c;各种3D打印新材料不断涌现&#xff0c;模具3D打印材料也开始重工艺导向逐步向性能导向发展&#xff0c;如毅速公司推出的ESU-EM191/191S及ESU-EM201不锈钢粉末、…

电脑文件误删除如何恢复?数据恢复第一步是什么?这五点要第一时间处理!

电脑文件误删除如何恢复&#xff1f;数据删除恢复的第一时间要做什么&#xff0c;你知道吗&#xff1f; 在使用电脑的过程中&#xff0c;误删除重要文件的情况时有发生。面对这种情况&#xff0c;不必过于慌张&#xff0c;因为有多种方法可以帮助你恢复误删除的文件。以下是恢复…

网络通信---UDP

前两天做了个mplayer项目&#xff0c;今日继续学习 网络内容十分重要&#xff01;&#xff01;&#xff01; 1.OSI七层模型 应用层:要传输的数据信息&#xff0c;如文件传输&#xff0c;电子邮件等&#xff08;最接近用户&#xff0c;看传输的内容类型到底是什么&#xff09; …

【数据结构与算法】顺序表

顺序表 一.顺序表的原理1.是什么2.数据结构 二.顺序表的初始化三.顺序表增加元素四.顺序表插入元素五.顺序表删除元素六.顺序表的销毁七.总结 一.顺序表的原理 1.是什么 顺序表是一种线性的结构,类似于数组,但是中间不能有空值. 元素顺序地存储在一段连续的内存空间中. 顺序表…

单关节电机动力学辨识

这是一个单关节电机的动力学辨识过程&#xff0c;这是一个yaw轴转动电机的动力学辨识过程 1、动力学建模 &#xff08;1&#xff09;整体动力学 F J α f F J\alpha f FJαf 单关节的物理量包括惯性项、离心力和科氏力、摩擦力。这里忽略离心力和科氏力&#xff0c;据说…

SolidEdge二次开发(C#)-环境配置

文章目录 1、前言2、环境配置2.1 安装Solidworks20242.2 安装VS20222.3 查看Com组件2.3.1 在VS2022中创建一个wpf工程项目2.3.2 添加com组件 1、前言 SolidEdge是Siemens PLM Software公司旗下的三维CAD软件&#xff0c;采用Siemens PLM Software公司自己拥有专利的Parasolid作…

js动画插件-vue

分享一个动画插件 学习 动画插件 是进入大厂的必备技能 首先我们需要先学会 去使用js 动画 封装好的 GreenSock 动画平台 &#xff08;GSAP&#xff09; greensock.com/gsap/ 就是这个插件 我现在分享一个用例 其实很简单 但是 具体的属性 和很多 内容需要慢慢使用 慢慢看…

C++客户端Qt开发——系统相关(文件操作)

2.文件操作 ①输入输出设备类 在Qt中&#xff0c;文件读写的类为QFile。QFile的父类为QFileDevice,QFileDevice提供了文件交互操作的底层功能。QFileDevice的父类是QIODevice,QIODevice的父类为QObject。 QIODevice是Qt中所有输入输出设备(input/output device,简称I/O设备)…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 环形字符串最长子串(200分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…