设计模式-代理模式Proxy
代理模式Proxy
- 代理模式 (Proxy)
- 1) 静态代理
- 1.a) 原理解析
- 1.b) 使用场景
- 1.c) 静态代理步骤总结
- 2) 动态代理
- 2.a) 基于 JDK 的动态代理实现步骤
- 2.b) 基于 CGLIB 的动态代理实现步骤
- 2.c) Spring中aop的使用步骤
代理模式 (Proxy)
代理设计模式(Proxy Design Pattern)是一种结构型设计模式,它为其他对象提供一个代理,以控制对这个对象的访问。代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。
在设计模式中,代理模式可以分为静态代理和动态代理。静态代理是指代理类在编译时就已经确定,而动态代理是指代理类在运行时动态生成。
1) 静态代理
1.a) 原理解析
在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
1.b) 使用场景
1.缓存代理
缓存代理通常会在内部维护一个缓存数据结构,如 HashMap 或者 LinkedHashMap,用来存储已经处理过的请求及其结果。
假设有一个数据查询接口,它从数据库或其他数据源中检索数据。在没有缓存代理的情况下,每次查询都需要访问数据库,这可能会导致较高的资源消耗和延迟。通过引入缓存代理,我们可以将查询结果存储在内存中,从而避免重复查询数据库。
public interface DataQuery {String query(String queryKey);
}
public class DatabaseDataQuery implements DataQuery {@Overridepublic String query(String queryKey) {// 使用数据源从数据库查询数据很慢return "result";}
}
创建一个缓存代理类,它同样实现了 DataQuery 接口,并在内部使用HashMap 作为缓存:
public class DatabaseDataQueryProxy implements DataQuery {// 实现缓存,需要数据结构private Map<String, String> cache = new HashMap<>(256);// 你代理谁,就要持有谁private DatabaseDataQuery dataQuery;public DatabaseDataQueryProxy() {// 1.屏蔽被代理对象this.dataQuery = new DatabaseDataQuery();}@Overridepublic String query(String queryKey) {// 2.对被代理对象的方法做增强// 2.1.查询缓存,命中则返回String result = cache.get(queryKey);if (result != null) {System.out.println("命中缓存,走缓存");return result;}// 2.2.未命中,则查询数据库result = dataQuery.query(queryKey);// 2.2.1.如果有结果,需要将结果保存到缓存中,再返回if (result != null) {cache.put(queryKey, result);}System.out.println("未命中,走持久层");return result;}
}
// 测试代码
@Test
void test() {DataQuery dataQuery = new DatabaseDataQueryProxy();String value = dataQuery.query("key1");System.out.println(value);value = dataQuery.query("key1");System.out.println(value);value = dataQuery.query("key2");System.out.println(value);
}
2.安全代理
用于控制对真实主题对象的访问。通过安全代理,可以实现访问控制、权限验证等安全相关功能。
假设我们有一个敏感数据查询接口,只有具有特定权限的用户才能访问:
3.虚拟代理
在需要时延迟创建耗时或资源密集型对象。虚拟代理在初始访问时才创建实际对象,之后将直接使用该对象。这可以避免在实际对象尚未使用的情况下就创建它,从而节省资源。
以下是一个虚拟代理的应用示例:
假设我们有一个大型图片类,它从网络加载图像。由于图像可能非常大,我们希望在需要显示时才加载它。为了实现这一点,我们可以创建一个虚拟代理来代表大型图片类。
4.远程代理
用于访问位于不同地址空间的对象。远程代理可以为本地对象提供与远程对象相同的接口,使得客户端可以透明地访问远程对象。通常,远程代理需要处理网络通信、序列化和反序列化等细节。
1.c) 静态代理步骤总结
通过前四个案例,我们也大致了解了静态代理的使用方式,其大致流程如下:
- 1.创建一个接口,定义 代理类和被代理类 实现共同的接口
- 2.创建被代理类,实现这个接口,并且在其中定义实现方法
- 3.创建代理类,也要实现这个接口,同时在其中定义一个被代理类的对象作为成员变量
- 4.在代理类中实现接口中的方法,方法中调用 被代理类 中的对应方法
- 5.通过创建代理对象,并调用其方法,方法增强
这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改。
2) 动态代理
静态代理需要手动编写代理类,代理类与被代理类实现相同的接口或继承相同的父类,对被代理对象进行包装。在程序运行前,代理类的代码就已经生成,并在程序运行时调用。静态代理的优点是简单易懂,缺点是需要手动编写代理类,代码复杂度较高,且不易扩展。
动态代理是在程序运行时动态生成代理类,无需手动编写代理类,大大降低了代码的复杂度。动态代理一般使用 Java 提供的反射机制实现,可以对任意实现了接口的类进行代理。动态代理的优点是灵活性高,可以根据需要动态生成代理类,缺点是性能相对较低,由于使用反射机制,在运行时会产生额外的开销。
2.a) 基于 JDK 的动态代理实现步骤
使用缓存代理的例子
public interface DataQuery {String query(String queryKey);
}
public class DatabaseDataQuery implements DataQuery {@Overridepublic String query(String queryKey) {// 使用数据源从数据库查询数据很慢return "result";}
}
创建一个代理类,实现 InvocationHandler 接口,实现invoke()方法,并在其中定义一个被代理类的对象作为属性。
public class CacheInvocationHandler implements InvocationHandler {private Map<String, String> cache = new HashMap<>(256);private DatabaseDataQuery databaseDataQuery;public CacheInvocationHandler() {this.databaseDataQuery = new DatabaseDataQuery();}public CacheInvocationHandler(DatabaseDataQuery databaseDataQuery) {this.databaseDataQuery = databaseDataQuery;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1.判断是哪一个方法 (只对query方法做缓存)String result;if ("query".equals(method.getName())) {// 2.查缓存// 2.1.命中直接返回result = cache.get(args[0].toString());if (result != null) {System.out.println("从缓存拿数据");return result;}// 2.2.未命中,查询数据库 (需要代理实例)result = (String) method.invoke(databaseDataQuery, args);// 3.查询到了,进行缓存cache.put(args[0].toString(), result);return result;}// 当其他的方法被调用,不希望被干预,直接调用原生的方法return method.invoke(databaseDataQuery, args);}
}
主要业务逻辑 (测试代码)
@Test
void testJdkDynamicProxy() {// jdk提供的代理实现,主要是使用Proxy类来实现// 参数1 classLoader:被代理类的类加载器ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 参数2 代理类需要实现的接口数组Class[] interfaces = new Class[]{DataQuery.class};// 参数3 InvocationHandlerInvocationHandler invocationHandler = new CacheInvocationHandler();DataQuery dataQuery = (DataQuery) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);// 调用query方法时,实际上是调用了invoke()方法String result = dataQuery.query("key1");System.out.println(result);result = dataQuery.query("key1");System.out.println(result);result = dataQuery.query("key2");System.out.println(result);System.out.println("-----------------");result = dataQuery.queryAll();System.out.println(result);
}
2.b) 基于 CGLIB 的动态代理实现步骤
基于 CGLIB 的动态代理需要使用 net.sf.cglib.proxy.Enhancer 类和 net.sf.cglib.proxy.MethodInterceptor 接口。
1.创建一个被代理类,定义需要被代理的方法 (以DatabaseDataQuery为例)
public class DatabaseDataQuery {public String query(String queryKey) {// 使用数据源从数据库查询数据很慢System.out.println("正在从数据库中查询数据");return "result";}public String queryAll() {System.out.println("正在从数据库中查询数据");return "query All result";}
}
2.创建一个方法拦截器类,实现 MethodInterceptor 接口,并在其中定义一个被代理类的对象作为属性。
- 在
intercept方法中,我们可以对被代理对象的方法进行增强
public class CacheMethodInterceptor implements MethodInterceptor {private Map<String, String> cache = new HashMap<>(256);private DatabaseDataQuery databaseDataQuery;public CacheMethodInterceptor() {this.databaseDataQuery = new DatabaseDataQuery();}public CacheMethodInterceptor(DatabaseDataQuery databaseDataQuery) {this.databaseDataQuery = databaseDataQuery;}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 1.判断是哪一个方法String result;if ("query".equals(method.getName())) {// 2.查询缓存,命中则直接返回result = cache.get(args[0].toString());if (result != null) {System.out.println("从缓存中提取数据");return result;}// 3.未命中,查询数据库result = (String) method.invoke(databaseDataQuery, args);// 4.缓存到缓存中cache.put(args[0].toString(), result);return result;}return method.invoke(databaseDataQuery, args);}
}
3.在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Enhancer.create 方法生成代理对象。
@Test
void testCgkibDynamicProxy() {// cglib通过enhancerEnhancer enhancer = new Enhancer();// 1.设置父类enhancer.setSuperclass(DatabaseDataQuery.class);// 2.设置一个方法拦截器,用来拦截方法enhancer.setCallback(new CacheMethodInterceptor());// 3.创建代理类DatabaseDataQuery databaseDataQuery = (DatabaseDataQuery) enhancer.create();String value = databaseDataQuery.query("key1");System.out.println(value);value = databaseDataQuery.query("key1");System.out.println(value);value = databaseDataQuery.query("key2");System.out.println(value);
}
2.c) Spring中aop的使用步骤
在 Spring 中,AOP(面向切面编程)提供了一种有效的方式来对程序中的多个模块进行横切关注点的处理,例如日志、事务、缓存、安全等。使用 Spring AOP,可以在程序运行时动态地将代码织入到目标对象中,从而实现对目标对象的增强。
Spring AOP 的使用步骤如下:
1.引入 AOP 相关依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.3.9.RELEASE</version>
</dependency>
2.在Main中开启自动代理@EnableAspectJAutoProxy
@SpringBootApplication
@EnableAspectJAutoProxy
public class Main {public static void main(String[] args) {SpringApplication.run(Main.class, args);}
}
3.定义接口和实现类,并将具体实现注入容器 (以DatabaseDataQuery为例)
// 接口
public interface DataQuery {String query(String queryKey);
}// 实现类
@Component
public class DatabaseDataQuery implements DataQuery {@Overridepublic String query(String queryKey) {// 使用数据源从数据库查询数据很慢System.out.println("正在从数据库中查询数据");return "result";}
}
4.定义切面类,对方法做增强
- @Pointcut() 对某包下的某个类的某个方法做增强:
..代表任意方法 - @Around() 定义增强
@Component
@Aspect
public class CacheAspectj {private static Map<String,String> cache = new ConcurrentHashMap<>(256);@Pointcut("execution(* com.dcy.structural.proxy.dynamicProxy.aop.impl.DatabaseDataQuery.query(..))")public void pointcut() {}@Around("pointcut()")public String around(ProceedingJoinPoint joinPoint) {// 1.查询缓存Object[] args = joinPoint.getArgs();String key = args[0].toString();// 1.1.命中则返回String result = cache.get(key);if (result != null) {System.out.println("数据从缓存中提取");return result;}// 2.未命中,查询数据库,实际上是调用被代理bean的方法try {result = joinPoint.proceed().toString();// 如果查询有结果,进行缓存cache.put(key, result);} catch (Throwable e) {throw new RuntimeException(e);}return result;}
}
5.测试用例
@SpringBootTest
public class AopTest {@Resourceprivate DataQuery dataQuery;@Testvoid testSpringAop() {String result = dataQuery.query("key1");System.out.println(result);result = dataQuery.query("key1");System.out.println(result);result = dataQuery.query("key2");System.out.println(result);}}
相关文章:
设计模式-代理模式Proxy
代理模式Proxy 代理模式 (Proxy)1) 静态代理1.a) 原理解析1.b) 使用场景1.c) 静态代理步骤总结 2) 动态代理2.a) 基于 JDK 的动态代理实现步骤2.b) 基于 CGLIB 的动态代理实现步骤2.c) Spring中aop的使用步骤 代理模式 (Proxy) 代理设计模式(Proxy Design Pattern&…...
如何使用CSS实现一个自适应等高布局?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用 Flexbox 布局⭐ 使用 Grid 布局⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏是为那些对Web开发…...
Google colab部署VITS——零门槛快速克隆任意角色声音
目录 序言 查看GPU配置 复制代码库并安装运行环境 选择预训练模型 上传视频链接(单个不应长于20分钟) 自动处理所有上传的数据 训练质量相关:实验发现目前使用CJ模型勾选ADD_AUXILIARY,对于中/日均能训练出最好的效果&#x…...
14 | Spark SQL 的 DataFrame API 读取CSV 操作
sales.csv 内容 date,category,product,full_name,sales 2023-01-01,Electronics,Laptop,John Smith,1200.0 2023-01-02,Electronics,Smartphone,Jane Doe,800.0 2023-01-03,Books,Novel,Michael Johnson,15.0 2023-01-04,Electronics,Tablet,Emily Wilson,450.0 2023-01-05,B…...
redis面试题二
redis如何处理已过期的元素 常见的过期策略 定时删除:给每个键值设置一个定时删除的事件,比如有一个key值今天5点过期,那么设置一个事件5点钟去执行,把它数据给删除掉(优点:可以及时利用内存及时清除无效数…...
虚拟现实(VR)和增强现实(AR)
虚拟现实(Virtual Reality,VR)和增强现实(Augmented Reality,AR)是两种前沿的计算机技术,它们正在改变人们与数字世界的互动方式。虚拟现实创造了一个计算机生成的全新虚拟环境,而增…...
如何使用ChatGPT提词器,看看这篇文章
ChatGPT提词器是一种强大的自然语言处理工具,可以帮助你提高创造性写作的效率和质量。本教程将向您介绍如何使用ChatGPT提词器,以获得有趣、吸引人的文章、故事或其他文本内容。 步骤1:访问ChatGPT提词器 首先,确保您已经访问了…...
vue3-vuex持久化实现
vue3-vuex持久化实现 一、背景描述二、实现思路1.定义数据结构2.存值3.取值4.清空 三、具体代码1.定义插件2.使用插件 四、最终效果 一、背景描述 有时候我们可能需要在vuex中存储一些静态数据,比如一些下拉选项的字典数据。这种数据基本很少会变化,所以…...
详解 SpringMVC 的 @RequestMapping 注解
文章目录 1、RequestMapping注解的功能2、RequestMapping注解的位置3、RequestMapping注解的value属性4、RequestMapping注解的method属性5、RequestMapping注解的params属性(了解)6、RequestMapping注解的headers属性(了解)7、Sp…...
类的静态成员变量 static member
C自学精简教程 目录(必读) 类的静态成员 static member 变量全局只有一份副本,不会随着类对象的创建而产生副本。 static 静态成员 在类的成员变量前面增加static关键字,表示这个成员变量是类的静态成员变量。 #include <iostream> using name…...
MVSNet (pytorch版) 搭建环境 运行dtu数据集重建 实操教程(图文并茂、超详细)
文章目录 1 准备工作1.1 下载源码1.2 测试集下载2 配置环境3 dtu数据集 重建演示3.1 重建效果查看4 补充解释4.1 bash 脚本文件超参数解释4.2 lists/dtu解释5 Meshlab查看三维点云时 ,使用技巧总结1 Meshlab查看三维点云时 ,换背景颜色2 Meshlab查看三维点云时,点云颜色很暗…...
Linux系统Ubuntu以非root用户身份操作Docker的方法
本文介绍在Linux操作系统Ubuntu版本中,通过配置,实现以非root用户身份,进行Docker各项操作的具体方法。 在文章Linux系统Ubuntu配置Docker详细流程(https://blog.csdn.net/zhebushibiaoshifu/article/details/132612560࿰…...
m4s格式转换mp4
先安装 ffmpeg,具体从官网可以查到,https://ffmpeg.org,按流程走。 转换代码如下,可以任意选择格式导出 import subprocess import osdef merge_audio_video(input_audio_path, input_video_path, output_mp4_path):# 构建 FFmpe…...
SQL sever中库管理
目录 一、创建数据库 1.1库界面方式 1.2SQL命令方式 二、修改数据库 2.1库界面方式 2.2SQL命令方式 三、删除数据库 3.1库界面方式 3.2SQL命令方式 四、附加和分离数据库 4.1附加和分离数据库概述 4.2作用 4.3附加和分离数据库方法 4.4示例 一、创建数据库 1.1库…...
模板方法模式简介
概念: 模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,将一些步骤延迟到子类中实现。该模式通过在抽象类中定义一个模板方法来控制算法的流程,并使用具体方法来实现其中的某些步骤。 特点: 定义了一个算…...
自动化运维工具-------Ansible(超详细)
一、Ansible相关 1、简介 Ansible是自动化运维工具,基于Python开发,分布式,无需客户端,轻量级,实现了批量系统配置、批量程序部署、批量运行命令等功能,ansible是基于模块工作的,本身没有批量部署的能力。真正具有批量部署的是a…...
计算机毕设 基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python
文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model5 最后 1 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要…...
Citespace、vosviewer、R语言的文献计量学 、SCI
文献计量学是指用数学和统计学的方法,定量地分析一切知识载体的交叉科学。它是集数学、统计学、文献学为一体,注重量化的综合性知识体系。特别是,信息可视化技术手段和方法的运用,可直观的展示主题的研究发展历程、研究现状、研究…...
linux操作系统的权限的深入学习
1.Linux权限的概念 Linux下有两种用户:超级用户(root)、普通用户。 超级用户:可以再linux系统下做任何事情,不受限制 普通用户:在linux下做有限的事情。 超级用户的命令提示符是“#”,普通用户…...
LeetCode——三数之和(中等)
题目 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 …...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
