[SSM]GoF之代理模式
目录
十四、GoF之代理模式
14.1对代理模式的理解
14.2静态代理
14.3动态代理
14.3.1JDK动态代理
14.3.2CGLIB动态代理
十四、GoF之代理模式
14.1对代理模式的理解
-
场景:拍电影的时候,替身演员去代理演员完成表演。这就是一个代理模式。
-
演员为什么要找替身呢?(为什么要使用代理模式?)
-
怕自己受伤。(保护自己)
-
自己完成不了高难度动作。(功能增强)
-
-
在java程序中代理模式的作用:
-
当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为。
-
需要给某个对象的功能进行增强的时候,可以考虑找一个代理进行增强。
-
A对象无法和B对象直接交互,也可以使用代理模式来解决。
-
-
代理模式中有三个角色:
-
目标对象(演员)
-
代理对象(替身演员)
-
目标对象和代理对象的公共接口。(演员和替身演员应该具有相同的行为动作)
-
-
为什么演员和替身演员要有相同的行为动作呢?
-
不想让观众知道是替身演员,这里的观众其实就是“客户端程序”。
-
-
使用代理模式,对于客户端程序来说,客户端是无法察觉到的,客户端在使用代理对象的时候就像在使用目标对象。

-
代理模式是GoF23种设计模式之一,属于结构化设计模式。
-
代理模式在代码实现上,包括两种形式:
-
静态代理
-
动态代理
-
14.2静态代理
OrderService接口
package com.hhb.proxy.service;
//订单业务接口
public interface OrderService {//代理对象和目标对象的公共接口//生产订单void generate();
//修改订单信息void modify();
//查看订单详情void detail();
}
OrderServiceImpl
package com.hhb.proxy.service;
public class OrderServiceImpl implements OrderService {/*** 问题:统计所有业务接口的每一个业务方法的耗时。* 解决方案一:硬编码,在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序* 缺点:1.违背OCP开闭原则 2.代码没有得到复用* 解决方案二:编写业务类的子类,让子类继承业务类,对每个业务方法进行重写* 缺点:1.虽然解决了OCP开闭原则,但是代码耦合度很高,因为采用了继承关系。* 2.代码没有得到复用* 解决方案三:代理模式* 优点:1.解决了OCP问题 2.采用代理模式的has a,可以降低耦合度** 目前使用的是静态代理,这个静态代理的缺点是:类爆炸* 解决方法:使用动态代理模式来解决这个问题。* 动态代理还是代理模式,只不过添加了字节码生成技术,可以在内存中为我们动态生成一个class字节码,这个字节码就是代理类* 在内存中动态生成字节码代理类的技术叫做:动态代理*/@Overridepublic void generate() {//目标方法//模拟生成订单的耗时try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成");}
@Overridepublic void modify() {//目标方法//模拟修改订单的耗时try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改");}
@Overridepublic void detail() {//目标方法//模拟查询订单的耗时try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("请看订单详情");}
}
OrderServiceProxy
package com.hhb.proxy.service;
//代理对象(代理对象和目标对象要具有相同的行为,就要实现同一个或同一些接口)
//客户端在使用代理对象的时候就像在使用目标对象一样。
public class OrderServiceProxy implements OrderService {//将目标对象作为代理对象的一个属性,这种关系叫做关联关系,比继承关系的耦合度低//代理对象中含有目标对象的引用。关联关系:has a//注意:这里要写一个公共接口类型,因为公共接口耦合度低private OrderService target;//这就是目标对象,目标对象一定实现了OrderService接口//创建代理对象的时候,传一个目标对象给代理对象。public OrderServiceProxy(OrderService target) {this.target = target;}
@Overridepublic void generate() {//代理方法//增强long begin = System.currentTimeMillis();//调用目标对象的目标方法target.generate();long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "");}
@Overridepublic void modify() {long begin = System.currentTimeMillis();target.modify();long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "");}
@Overridepublic void detail() {long begin = System.currentTimeMillis();target.detail();long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "");}
}
客户端
package com.hhb.proxy.client;
import com.hhb.proxy.service.OrderServiceImpl;
import com.hhb.proxy.service.OrderServiceProxy;
public class Test {public static void main(String[] args) {//创建目标对象OrderServiceImpl target = new OrderServiceImpl();//创建代理对象OrderServiceProxy proxy = new OrderServiceProxy(target);//调用代理对象的代理方法proxy.generate();proxy.modify();proxy.detail();}
}
-
以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。
-
OrderServiceImpl是目标类,OrderServiceProxy是代理类。
14.3动态代理
-
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题。
-
在内存当中动态生成类的技术常见的包括:
-
JDK动态代理技术:只能代理接口。
-
CGLIB动态代理技术:CGLIB是一个开源项目,是一个强大的、高性能、高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
-
Javassist动态代理技术。
-
14.3.1JDK动态代理
OrderService接口
package com.hhb.proxy.service;
//订单业务接口
public interface OrderService {//代理对象和目标对象的公共接口//生产订单void generate();
//修改订单信息void modify();
//查看订单详情void detail();
}
OrderServiceImpl
package com.hhb.proxy.service;
public class OrderServiceImpl implements OrderService {@Overridepublic void generate() {//目标方法//模拟生成订单的耗时try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成");}
@Overridepublic void modify() {//目标方法//模拟修改订单的耗时try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改");}
@Overridepublic void detail() {//目标方法//模拟查询订单的耗时try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("请看订单详情");}
}
-
在动态代理中,OrderServiceProxy代理类是可以动态生成的,这个类不需要写,直接写客户端程序即可。
Client
package com.hhb.proxy.client;
import com.hhb.proxy.service.OrderService;
import com.hhb.proxy.service.OrderServiceImpl;
import com.hhb.proxy.service.TimerInvocationHandler;
import com.hhb.proxy.util.ProxyUtil;
import java.lang.reflect.Proxy;
public class Client {//客户端程序public static void main(String[] args) {//创建目标对象OrderService target = new OrderServiceImpl();//创建代理对象/* OrderService proxyObj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));*///使用工具类OrderService proxyObj = (OrderService) ProxyUtil.newProxyInstance(target);//调用代理对象的代理方法proxyObj.modify();proxyObj.detail();proxyObj.generate();String name = proxyObj.getName();System.out.println(name);}
}
-
创建代理对象
-
newProxyInstance翻译为:新建代理对象,也就是说,通过调用这个方法可以创建代理对象。本质上,这个Proxy.newProxyInstance()方法的执行做了两件事:
-
在内存中动态的生成了一个代理类的字节码class。
-
new对象了,通过内存中生成的代理类这个代码,实例化了代理对象。
-
-
关于newProxyInstance()方法的三个重要的参数:
-
第一个参数:ClassLoader loader
-
它是类加载器。在内存中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器,所以这里需要指定类加载器。并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。
-
-
第二个参数:Class<?>[] interfaces
-
代理类和目标类要实现同一个接口或同一些接口。
-
在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。
-
-
第三个参数:InvocationHandler h
-
InvocationHandler 被翻译为:调用处理器,是一个接口。
-
在调用处理器接口中编写的就是:增强代码。
-
既然是接口,就要写接口的实现类。
-
-
-
TimerInvocationHandler
package com.hhb.proxy.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
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) + "毫秒");//注意:这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回return retValue;}
}
-
为什么要强行要求你必须实现InvocationHandler接口?
-
因为一个类实现接口就必须实现接口中的方法。
-
方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
-
注意:invoke方法不是程序员调用的,是JDK负责调用。
-
-
invoke方法什么时候被调用?
-
当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。
-
-
invoke方法的三个参数
-
invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数,可以在invoke方法的大括号中直接使用。
-
第一个参数:Object proxy 代理对象的引用,这个参数使用较少。
-
第二个参数:Method method 目标对象上的目标方法。
-
第三个参数:Object[] args 目标方法上的实参。
-
invoke方法执行过程中,使用method来调用目标对象的目标方法。
-
工具类:ProxyUtil
package com.hhb.proxy.util;
import com.hhb.proxy.service.TimerInvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyUtil {/*** 封装一个工具方法,可以通过这个方法获取代理对象** @param target* @return*/public static Object newProxyInstance(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));}
}
14.3.2CGLIB动态代理
-
CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现,所以被代理的目标类不能使用final修饰。
引入依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
UserService
package com.hhb.proxy.service;
//目标类
public class UserService {//目标方法public boolean login(String username, String password) {System.out.println("系统正在验证身份");if ("admin".equals(username) && "123".equals(password)) {return true;}return false;}
//目标方法public void logout() {System.out.println("系统正在退出");}
}
-
使用CGLIB在内存中为UserService类生成代理类,并创建对象:
Client
package com.hhb.proxy.client;
import com.hhb.proxy.service.TimerMethodInterceptor;
import com.hhb.proxy.service.UserService;
import net.sf.cglib.proxy.Enhancer;
public class Client {public static void main(String[] args) {//创建字节码增强器对象//这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类Enhancer enhancer = new Enhancer();
//告诉CGLIB父类是谁,告诉CGLIB目标类是谁enhancer.setSuperclass(UserService.class);
//设置回调(等同于JDK动态代理当中的调用处理器。InvocationHandler)//在CGLIB当中不是InvocatioHandler接口,是方法拦截器:MethodInterceptorenhancer.setCallback(new TimerMethodInterceptor());
//创建代理对象//1.在内存中生成UserService类的子类,其实就是代理类的字节码。//2.创建代理对象UserService userServiceProxy = (UserService) enhancer.create();
//调用代理对象的代理方法boolean succes = userServiceProxy.login("admin", "123");System.out.println(succes ? "登录成功" : "登录失败");
userServiceProxy.logout();}
}
编写MethodInterceptor接口实现类
package com.hhb.proxy.service;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TimerMethodInterceptor implements MethodInterceptor {@Overridepublic 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;}
}
相关文章:
[SSM]GoF之代理模式
目录 十四、GoF之代理模式 14.1对代理模式的理解 14.2静态代理 14.3动态代理 14.3.1JDK动态代理 14.3.2CGLIB动态代理 十四、GoF之代理模式 14.1对代理模式的理解 场景:拍电影的时候,替身演员去代理演员完成表演。这就是一个代理模式。 演员为什…...
桥梁安全生命周期监测解决方案
一、方案背景 建筑安全是人们生产、经营、居住等经济生活和人身安全的基本保证,目前我国越来越多的建筑物逐 步接近或者已经达到了使用年限,使得建筑物不断出现各种安全隐患,对居民的人身安全和财产安全产 生不利影响,因此房…...
图技术在 LLM 下的应用:知识图谱驱动的大语言模型 Llama Index
LLM 如火如荼地发展了大半年,各类大模型和相关框架也逐步成型,可被大家应用到业务实际中。在这个过程中,我们可能会遇到一类问题是:现有的哪些数据,如何更好地与 LLM 对接上。像是大家都在用的知识图谱,现在…...
SpringBoot自动配置、启动器原理爆肝解析(干货满满)
文章目录 前言一、SpringBoot优势概要二、SpringBoot自动配置1. ☠注意☠2.自动配置详解 三、Starter(场景启动器)原理总结 前言 本文详细解析面试重点—SpringBoot自动配置原理、场景启动器原理,深入源码,直接上干货、绝不拖泥带…...
chrome扩展控制popup页面动态切换
文章目录 1、通过控制元素的显示隐藏达到popup页面切换的效果2、通过监听页面重新加载完成不同popup的切换3、直接修改popup页面location.href,无需刷新页面 1、通过控制元素的显示隐藏达到popup页面切换的效果 下面在mv2版本的API下完成 实际上通过控制页面元素实…...
【AI】《动手学-深度学习-PyTorch版》笔记(三):PyTorch常用函数
AI学习目录汇总 1、torch.arange 返回一维张量(一维数组),官网说明,常见的三种用法如下 输入:torch.arange(5) 输出:tensor([0, 1, 2, 3, 4]) 输入:torch.arange(5, 16) 输出:tensor([ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) 输入:torch.arange(1, 25, 2) …...
某文化馆三维建模模型-glb格式-三维漫游-室内导航测试
资源描述 某文化馆某个楼层的三维建模模型,glb格式,适用于three.js开发,可用来做一些三维室内漫游测试和室内导航测试 资源下载地址...
网络安全 Day19-计算机网络基础知识04(网络协议)
计算机网络基础知识04(网络协议) 1. ARP1.1 ARP通讯原理1.2 arp欺骗1.3 ARP欺骗与预防1.4 排查ARP病毒 2. DHCP工作原理(自动分配内网IP)3. TCP协议三次握手、四次挥手原理4. DNS协议工作原理 1. ARP Linux查看arp:ar…...
Verilog语法学习——LV5_位拆分与运算
LV5_位拆分与运算 题目来源于牛客网 [牛客网在线编程_Verilog篇_Verilog快速入门 (nowcoder.com)](https://www.nowcoder.com/exam/oj?page1&tabVerilog篇&topicId301) 题目 题目描述: 现在输入了一个压缩的16位数据,其实际上包含了四个数据…...
❤️创意网页:创意动态画布~缤纷移动涂鸦~图片彩色打码
✨博主:命运之光 🌸专栏:Python星辰秘典 🐳专栏:web开发(简单好用又好看) ❤️专栏:Java经典程序设计 ☀️博主的其他文章:点击进入博主的主页 前言:欢迎踏入…...
数值分析第六章节 用Python实现解线性方程组的迭代法
参考书籍:数值分析 第五版 李庆杨 王能超 易大义编 第5章 解线性方程组的迭代法 文章声明:如有发现错误,欢迎批评指正 文章目录 迭代法的基本概念雅可比迭代法与高斯-塞格尔迭代法雅可比迭代法高斯-塞格尔迭代法 迭代法的基本概念 6.1.1引言…...
【低代码专题方案】使用iPaaS平台下发数据,快捷集成MDM类型系统
01 场景背景 伴随着企业信息化建设日趋完善化、体系化,使用的应用系统越来越多,业务发展中沉淀了大量数据。主数据作为数据治理中枢,保存大量标准数据库,如何把庞大的数据下发到各个业务系统成了很棘手的问题。 传统的数据下发方…...
驱动开发 day3 (模块化驱动启动led,蜂鸣器,风扇,震动马达)
模块化驱动启动led,蜂鸣器,风扇,震动马达并加上Makefile 封装模块化驱动,可自由安装卸载驱动,便于驱动更新(附图) 1.安装模块驱动同时初始化各个设备并使能 2.该驱动会自动创建驱动节点. 3.通过c函数程序输入控制各个设备 4.卸载模块驱动 //编译驱动…...
数据结构与算法基础-学习-27-图之最短路径之Dijkstra(迪杰斯特拉)算法
一、最短路径应用案例 例如从北京到上海旅游,有多条路可以到目的地,哪条路线最短,哪条路线最省钱,就是典型的最短路径问题。 二、最短路径问题分类 最短路径问题可以分为两类,第一类为:两点间最短路径。第…...
Windows Server 2012 能使用的playwright版本
由于在harkua_bot里面使用到了playwright,我的服务器又是Windows Server 2012 R2,最新版playwright不支持Windows Server 2012 R2,支持Windows Server 2016以上,所以有了这个需求 https://cdn.npmmirror.com/binaries/playwright…...
css实现溢出变为省略号
单行文本溢出省略 text-overflow:规定当文本溢出时,显示省略符号来代表被修剪的文本 white-space:设置文字在一行显示,不能换行 overflow:文字长度超出限定宽度,则隐藏超出的内容overflow设为hidden&#…...
nginx如何配置两个服务器的连接
nginx 中通过server_name listen的方式配置多个服务器 nginx配置两个站点的windows操作方法,双域名双站点...
Linux环境Arduino IDE中配置ATOM S3
linux选择ubuntu发行版。 硬件设备有多小呢: 功能超级强大。 之前的ROS1和ROS2案例已经全部移植完成并测试结束(三轮纯人力校验😎)。 官网文档信息非常非常好: https://docs.m5stack.com/zh_CN/quick_start/atoms3…...
【C#】.Net Framework框架下的Authorize权限类
2023年,第31周,第3篇文章。给自己一个目标,然后坚持总会有收货,不信你试试! 在C#的.NET Framework中,你可以使用Authorize类来处理权限认证。Authorize类位于System.Web.Mvc命名空间中,它提供了…...
C++ list底层实现原理
文章目录 一、list底层实现二、类构成三、构造函数四、迭代器五、获取第一个元素六、获取最后一个元素七、插入元素 一句话:list底层实现一个双向循环链表 一、list底层实现 一个双向循环链表 二、类构成 class list : protected_List_base_list_base.lsit_impl…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
