目录

静态代理

动态代理

JDK动态代理

CGLIB代理

JDK动态代理和CGLIB代理的区别

总结


代理模式简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,扩展目标对象的功能。

代理模式有静态代理和动态代理两种实现方式

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。

1.定义发送短信的接口

public interface SmsService {String send(String message);
}

2.实现发送短信的接口

public class SmsServiceImpl implements SmsService {public String send(String message) {System.out.println("send message:" + message);return message;}
}

3.创建代理类并同样实现发送短信的接口

public class SmsProxy implements SmsService {private final SmsService smsService;public SmsProxy(SmsService smsService) {this.smsService = smsService;}@Overridepublic String send(String message) {//调用方法之前,我们可以添加自己的操作System.out.println("before method send()");smsService.send(message);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method send()");return null;}
}

4.实际使用

public class Main {public static void main(String[] args) {SmsService smsService = new SmsServiceImpl();SmsProxy smsProxy = new SmsProxy(smsService);smsProxy.send("java");}
}

运行上述代码之后,控制台打印出:

before method send()
send message:java
after method send()

动态代理

动态代理更加灵活。我们不需要对每个目标类都单独创建一个代理类(JDK动态代理机制),并且也不需要代理类实现接口,我们可以直接代理目标类( CGLIB 动态代理机制)。从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

JDK动态代理

JDK动态代理只能代理接口,底层是采用实现接口的方式实现的
例子:一个接口和一个实现类。

OrderService接口

/*** 订单接口**/
public interface OrderService {/*** 生成订单*/void generate();/*** 查看订单详情*/void detail();/*** 修改订单*/void modify();
}

OrderService接口实现类

public class OrderServiceImpl implements OrderService {@Overridepublic void generate() {try {Thread.sleep(1234);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成");}@Overridepublic void detail() {try {Thread.sleep(2541);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单信息如下:******");}@Overridepublic void modify() {try {Thread.sleep(1010);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改");}
}

我们在静态代理的时候,除了以上一个接口和一个实现类之外,是不是要写一个代理类UserServiceProxy呀!在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:
 

public class Client {public static void main(String[] args) {// 第一步:创建目标对象OrderService target = new OrderServiceImpl();// 第二步:创建代理对象OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);// 第三步:调用代理对象的代理方法orderServiceProxy.detail();orderServiceProxy.modify();orderServiceProxy.generate();}
}

以上第二步创建代理对象是需要大家理解的: OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);

这行代码做了两件事:

●第一件事:在内存中生成了代理类的字节码
●第二件事:创建代理对象

Proxy类全名:java.lang.reflect.Proxy。这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。

其中newProxyInstance()方法有三个参数:

●第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
●第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
●第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。

所以接下来我们要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:

public class TimerInvocationHandler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}
}

InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:

●第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
●第二个参数:Method method。目标方法。
●第三个参数:Object[] args。目标方法调用时要传的参数。

我们将来肯定是要调用“目标方法”的,但要调用目标方法的话,需要“目标对象”的存在,“目标对象”从哪儿来呢?我们可以给TimerInvocationHandler提供一个构造方法,可以通过这个构造方法传过来“目标对象”,代码如下:

public class TimerInvocationHandler implements InvocationHandler {// 目标对象private Object target;// 通过构造方法来传目标对象public TimerInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}
}

有了目标对象我们就可以在invoke()方法中调用目标方法了。代码如下:

public class TimerInvocationHandler implements InvocationHandler {// 目标对象private Object target;// 通过构造方法来传目标对象public TimerInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 目标执行之前增强。long begin = System.currentTimeMillis();// 调用目标对象的目标方法Object retValue = method.invoke(target, args);// 目标执行之后增强。long end = System.currentTimeMillis();System.out.println("耗时"+(end - begin)+"毫秒");// 一定要记得返回哦。return retValue;}
}

到此为止,调用处理器就完成了。接下来,应该继续完善Client程序:

public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderServiceImpl();// 创建代理对象OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));// 调用代理对象的代理方法orderServiceProxy.detail();orderServiceProxy.modify();orderServiceProxy.generate();}
}

大家可能会比较好奇:那个InvocationHandler接口中的invoke()方法没看见在哪里调用呀?

注意:当你调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用。也就是上面代码调用方法的这三行代码中任意一行代码执行,注册在InvocationHandler接口中的invoke()方法都会被调用。

执行结果:

学到这里可能会感觉有点懵,折腾半天,到最后这不是还得写一个接口的实现类吗?没省劲儿呀?
你要这样想就错了!!!!

我们可以看到,不管你有多少个Service接口,多少个业务类,这个TimerInvocationHandler接口是不是只需要写一次就行了,代码是不是得到复用了!!!!

而且最重要的是,以后程序员只需要关注核心业务的编写了,像这种统计时间的代码根本不需要关注。因为这种统计时间的代码只需要在调用处理器中编写一次即可。

到这里,JDK动态代理的原理就结束了。

CGLIB代理

CGLIB动态代理既可以代理接口,也可以代理类,底层是使用继承实现,所以既然是继承,那被代理的目标类就不能用final修饰。

我们准备一个没有实现接口的类(目标类),如下:

public class UserService {public void login(){System.out.println("用户正在登录系统....");}public void logout(){System.out.println("用户正在退出系统....");}
}

使用CGLIB在内存中为UserService类生成代理类,并创建对象:

public class Client {public static void main(String[] args) {// 创建字节码增强器Enhancer enhancer = new Enhancer();// 告诉cglib要继承哪个类enhancer.setSuperclass(UserService.class);// 设置回调接口enhancer.setCallback(方法拦截器对象);// 生成源码,编译class,加载到JVM,并创建代理对象UserService userServiceProxy = (UserService)enhancer.create();userServiceProxy.login();userServiceProxy.logout();}
}

和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是net.sf.cglib.proxy.MethodInterceptor

编写MethodInterceptor接口实现类:

public class TimerMethodInterceptor implements MethodInterceptor {@Override//target目标对象,method:目标方法,//objects:目标方法调用时的实参,methodProxy:代理方法public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 前增强long begin = System.currentTimeMillis();// 调用目标Object retValue = methodProxy.invokeSuper(target, objects);// 后增强long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");// 一定要返回return retValue;}
}

回调已经写完了,可以修改客户端程序了:

public class Client {public static void main(String[] args) {// 创建字节码增强器Enhancer enhancer = new Enhancer();// 告诉cglib要继承哪个类enhancer.setSuperclass(UserService.class);// 设置回调接口enhancer.setCallback(new TimerMethodInterceptor());// 生成源码,编译class,加载到JVM,并创建代理对象UserService userServiceProxy = (UserService)enhancer.create();userServiceProxy.login();userServiceProxy.logout();}
}

执行结果:

JDK动态代理和CGLIB代理的区别

  • JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  • 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

总结

这篇文章中主要介绍了代理模式的两种实现:静态代理以及动态代理。涵盖了静态代理和动态代理实战、静态代理和动态代理的区别、JDK 动态代理和 Cglib 动态代理区别等内容。

如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!!

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

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

相关文章

MCU AI/ML - 弥合智能和嵌入式系统之间的差距

作者:芯科科技产品营销高级经理Gopinath Krishniah 人工智能(AI)和机器学习(ML)是使系统能够从数据中学习、进行推理并随着时间的推移提高性能的关键技术。这些技术通常用于大型数据中心和功能强大的GPU,但…

Redis中的sdshdr的len和alloc那块的知识点详解

文章目录核心比喻:一个可以伸缩的水瓶场景一:创建一个新字符串场景二:追加字符串(触发“空间预分配”)场景三:再次追加字符串(利用空闲空间)场景四:缩短字符串&#xff0…

在Linux下访问MS SQL Server数据库

Linux作为一个免费的Unix类操作系统,以其开放性源代码、多任务、X window等特点为众多的用户所采用,并有很多企业采用Linux来作为其内部网的全功能服务器(WWW,FTP,Email、DNS)。企业的内部网不仅要提供文本信息的访问,…

计算机视觉-OpenCV

一下载第三方库opencv-python3.4.18.65opencv-contrib-python3.4.18.65import cv2 # 读取的格式是BGR numpy import numpy as np# 读取图片 a cv2.imread(generated_image.jpg) # 读取图片 print(a) # NumPy数组,其中存储了读取的图像文件的像素值。cv2.imshow…

解决GitHub无法打开

找到下图文件,用记事本打开 在最下方粘贴如下代码140.82.113.4 github.com 20.205.243.166 github.com 140.82.112.4 github.com 151.101.1.6 github.global.ssl.fastly.net 185.199.108.153 assets-cdn.github.com 185.199.109.153 assets-cdn.github.com 185.199.…

AWS VPC Transit Gateway 可观测最佳实践

AWS VPC Transit Gateway 介绍 Amazon VPC Transit Gateway 是一个网络传输中心,用于互连虚拟私有云 (VPCs) 和本地网络。随着您的云基础设施在全球扩展,区域间对等互连使用 AWS 全球基础设施将中转网关连接在一起。 AWS 数据中心之间的所有网络流量都在…

WeakRef的作用和使用

文章目录WeakRef的作用和使用使用 WeakRef 避免强引用:原理与实践一、WeakRef 的核心特性二、WeakRef 与强引用的对比三、WeakRef 的使用场景与示例1. 非关键数据缓存(避免缓存导致内存泄漏)2. 跟踪对象生命周期(不干扰回收&#…

【华为机试】332. 重新安排行程

文章目录332. 重新安排行程题目描述示例 1:示例 2:提示:解题思路核心思路算法流程图欧拉路径原理DFS回溯机制字典序优化策略复杂度分析算法实现要点完整题解代码332. 重新安排行程 题目描述 给你一份航线列表 tickets ,其中 tic…

通信算法之300:CRC表生成方式-CRC8、CRC16、CRC32-输入字节

"CRC表的MATLAB生成代码"生成的查找表可以用于快速计算 CRC 值,通过查表法可以显著提高 CRC 计算效率,尤其适用于需要处理大量数据的场景。下面是一个生成 CRC 查找表(CRC Table)的 MATLAB 代码,该代码可以根…

国内使用 npm 时配置镜像源

在国内使用 npm 时,由于网络限制可能会遇到下载速度慢或连接超时的问题。通过设置国内镜像源,可以显著提升下载速度和稳定性。以下是常用的国内 npm 镜像源及其配置方法。 查询当前使用的镜像源 npm get registry 设置为淘宝镜像源 npm config set reg…

一篇文章入门TCP与UDP(保姆级别)

🐳第一部分:什么是TCP和UDP? 先给结论:TCP 和 UDP 都是传输层协议,负责把数据从一台电脑 “搬” 到另一台电脑,但它们的 “搬运风格” 完全不同 📦 比喻:TCP 像 "打电话"&#xff…

2024年测绘程序设计比赛--空间探索性分析(数据为2025年第三次模拟数据)

想要在2026年参加这个比赛的&#xff0c;可以加入小编和其它大佬所建的群242845175一起来备赛&#xff0c;为2026年的比赛打基础&#xff0c;也可以私信小编&#xff0c;为你答疑解惑一、读写文件 internal class Read {public static List<Point> pts new List<Poin…

力扣 hot100 Day68

84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 class Solution { public:int largestRectangleArea(vector<int>&…

生成式AI时代,Data+AI下一代数智平台建设指南

DataAI下一代数智平台建设指南一、生成式AI时代的五大数据挑战二、驱动DataAI平台建设的核心要素主动选择&#xff1a;构建竞争壁垒被动应对&#xff1a;解决现有痛点三、DataAI平台的六大关键能力四、腾讯云DataAI产品方案与实践1. 数据与AI协同层2. 开发与治理层3. 存储与计算…

FPGA学习笔记——SPI通讯协议简介

目录 一、SPI通讯协议简介 二、SPI物理层 三、SPI协议层 1.通讯模式 &#xff08;一&#xff09;模式零 &#xff08;二&#xff09;模式一 &#xff08;三&#xff09;模式二 &#xff08;四&#xff09;模式三 2.通讯流程 一、SPI通讯协议简介 SPI&#xff08;Seria…

JavaScript核心概念解析:从基础语法到对象应用

导语&#xff1a;本文系统梳理JavaScript的核心知识框架&#xff0c;适用于编程入门学习者。内容涵盖基础语法、数据类型、函数应用及内置对象&#xff0c;帮助读者构建清晰的JS知识体系。一、语言基础与执行原理浏览器执行机制渲染引擎&#xff1a;解析HTML/CSS&#xff08;如…

在 Kotlin 中使用函数类型和 lambda 表达式

参考官方文档: https://developer.android.google.cn/codelabs/basic-android-kotlin-compose-function-types-and-lambda?hl=zh-cn#0 1、 将函数存储在变量中 作为一种一级结构,函数也属于数据类型,因此,可以将函数存储在变量中、将函数传递到函数,以及从函数返回函数…

计算机硬件组成原理

&#x1f9e0; 一、计算机的硬件组成&#xff1a;五大核心部件 根据“冯诺依曼体系结构”&#xff0c;现代计算机主要由这 5大部分组成&#xff1a;部件作用通俗解释1️⃣ 运算器&#xff08;ALU&#xff09;负责算术和逻辑运算会加减乘除和做判断的“计算工厂”2️⃣ 控制器&a…

告别 window.open,拥抱全新浮窗体验!

深入了解 Document Picture-in-Picture API&#xff0c;并对比 Modal 的最佳使用场景在前端开发中&#xff0c;我们经常会遇到这样的需求&#xff1a;弹出一个浮动窗口来显示一些实时信息、工具栏或视频内容。过去我们会用 window.open()&#xff0c;后来越来越多的开发者倾向于…

Python爬虫实战:研究weiboSpider技术,构建新浪微博数据采集系统

1. 引言 1.1 研究背景 在信息时代,社交媒体已成为人们获取信息、表达观点的重要渠道。微博作为其中的典型代表,拥有庞大的用户群体和活跃的内容生态。截至 2023 年底,微博月活跃用户数已超过 5.8 亿,日均发博量达数千万条,数据涵盖社会热点、公众情绪、消费偏好等多维度…