深入理解代理模式(Proxy):静态代理、动态代理与AOP
目录
- 1. 代理模式简介
- 2. 静态代理
- 3. 动态代理
- 3.1 JDK动态代理
- 3.2 CGLIB动态代理
- 4. 面向切面编程(AOP)
- 5. 实战示例
- 6. 总结与最佳实践
1. 代理模式简介
代理模式是一种结构型设计模式,它允许我们提供一个代理来控制对其他对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
代理模式的UML类图
代理模式的主要角色
- Subject(抽象主题):定义代理类和真实主题的共同接口
- RealSubject(真实主题):定义代理所代表的真实对象
- Proxy(代理):包含对真实主题的引用,从而可以操作真实主题
2. 静态代理
静态代理是最简单的代理模式实现方式。代理类在编译时就已经确定,在程序运行前就已经存在代理类的 .class 文件。
示例代码
// 抽象主题接口
public interface UserService {void addUser(String username);void deleteUser(String username);
}// 真实主题类
public class UserServiceImpl implements UserService {@Overridepublic void addUser(String username) {System.out.println("添加用户: " + username);}@Overridepublic void deleteUser(String username) {System.out.println("删除用户: " + username);}
}// 静态代理类
public class UserServiceProxy implements UserService {private UserService userService;public UserServiceProxy(UserService userService) {this.userService = userService;}@Overridepublic void addUser(String username) {System.out.println("开始添加用户...");long startTime = System.currentTimeMillis();userService.addUser(username);long endTime = System.currentTimeMillis();System.out.println("添加用户完成,耗时:" + (endTime - startTime) + "ms");}@Overridepublic void deleteUser(String username) {System.out.println("开始删除用户...");long startTime = System.currentTimeMillis();userService.deleteUser(username);long endTime = System.currentTimeMillis();System.out.println("删除用户完成,耗时:" + (endTime - startTime) + "ms");}
}
开始添加用户...
添加用户完成,耗时:1ms
开始删除用户...
删除用户完成,耗时:1ms
介绍:静态代理通过代理类为目标方法添加了额外的功能(如记录执行时间),并且不需要修改目标类的代码。上述输出表示在添加用户和删除用户时,代理类能够控制方法执行并统计耗时。
静态代理的优缺点
优点:
- 可以在不修改目标对象的前提下,实现对目标对象的功能扩展
- 实现简单,容易理解
缺点:
- 为每个服务都需要创建代理类,工作量大
- 不易维护,一旦接口增加方法,所有代理类都需要修改
3. 动态代理
动态代理是在程序运行时,通过反射机制动态创建代理类的代理模式实现方式。
3.1 JDK动态代理
JDK动态代理只能代理实现了接口的类。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 动态代理处理器
public class LogHandler implements InvocationHandler {private Object target;public LogHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开始执行方法:" + method.getName());long startTime = System.currentTimeMillis();Object result = method.invoke(target, args);long endTime = System.currentTimeMillis();System.out.println("方法执行完成,耗时:" + (endTime - startTime) + "ms");return result;}// 获取代理对象public static Object getProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new LogHandler(target));}
}// 使用示例
public class DynamicProxyDemo {public static void main(String[] args) {UserService userService = new UserServiceImpl();UserService proxy = (UserService) LogHandler.getProxy(userService);proxy.addUser("张三");proxy.deleteUser("张三");}
}
开始执行方法:addUser
方法执行完成,耗时:1ms
开始执行方法:deleteUser
方法执行完成,耗时:1ms
介绍:使用JDK动态代理,代理对象的行为由InvocationHandler接口的invoke方法决定。这里我们添加了日志记录功能,输出了执行方法的时间统计。
3.2 CGLIB动态代理
CGLIB可以代理没有实现接口的类,通过继承被代理类来实现代理。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;public class CglibProxy implements MethodInterceptor {private Object target;public CglibProxy(Object target) {this.target = target;}public Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("开始执行方法:" + method.getName());long startTime = System.currentTimeMillis();Object result = proxy.invokeSuper(obj, args);long endTime = System.currentTimeMillis();System.out.println("方法执行完成,耗时:" + (endTime - startTime) + "ms");return result;}
}// 使用示例
public class CglibProxyDemo {public static void main(String[] args) {UserService userService = new UserServiceImpl();UserService proxy = (UserService) new CglibProxy(userService).getProxy();proxy.addUser("李四");proxy.deleteUser("李四");}
}
开始执行方法:addUser
方法执行完成,耗时:1ms
开始执行方法:deleteUser
方法执行完成,耗时:1ms
介绍:CGLIB动态代理通过继承目标类来生成代理类,它不要求目标类实现接口。类似于JDK动态代理,这里输出的日志同样记录了方法执行的时间。
4. 面向切面编程(AOP)
AOP是一种编程范式,它通过预编译方式和运行期动态代理实现程序功能的统一维护。
Spring AOP示例
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {@Around("execution(com.example.service..(..))")public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = joinPoint.proceed();long endTime = System.currentTimeMillis();System.out.println(joinPoint.getSignature() + " 执行耗时:" + (endTime - startTime) + "ms");return result;}@Before("execution(com.example.service..(..))")public void logBefore() {System.out.println("方法执行前的处理...");}@After("execution(com.example.service..(..))")public void logAfter() {System.out.println("方法执行后的处理...");}
}
5. 实战示例
下面是一个完整的实战示例,展示如何使用代理模式实现方法执行时间统计和日志记录。
// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
}// 切面类
@Aspect
@Component
public class MethodExecutionAspect {private static final Logger logger = LoggerFactory.getLogger(MethodExecutionAspect.class);@Around("@annotation(LogExecutionTime)")public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = joinPoint.proceed();long endTime = System.currentTimeMillis();logger.info("方法 [{}] 执行耗时:{}ms",joinPoint.getSignature().getName(), (endTime - startTime));return result;}
}// 使用示例
@Service
public class UserServiceImpl implements UserService {@LogExecutionTime@Overridepublic void addUser(String username) {// 业务逻辑}
}
方法 [addUser] 执行耗时:2ms
介绍:该示例使用了自定义注解@LogExecutionTime来标记需要记录执行时间的方法。Spring AOP通过切面对这些方法进行增强,输出了方法名称及其执行的耗时。
6. 总结与最佳实践
代理模式是一种非常实用的设计模式,它可以帮助我们在不修改原有代码的情况下,添加新的功能:
- 静态代理
- 简单直观,但是代理类需要手动编写
- 适用于代理类较少的场景
- JDK动态代理
- 只能代理实现了接口的类
- 运行时动态生成代理类
- 灵活性高
- CGLIB动态代理
- 可以代理未实现接口的类
- 通过继承实现
- 性能较好
- Spring AOP
- 提供了更高层次的抽象
- 支持声明式代理
- 结合了动态代理的优点
使用建议
- 如果目标类有接口,优先使用JDK动态代理
- 如果目标类没有接口,使用CGLIB动态代理
- 在Spring框架中,优先使用Spring AOP,它能自动选择合适的代理方式
注意事项
- 代理模式会增加系统的复杂度
- 动态代理可能会带来一定的性能开销
- 使用CGLib时注意final方法无法被代理
- 在使用Spring AOP时要注意代理对象的作用域
代理模式是Java开发中非常重要的一个设计模式,掌握好它可以帮助我们写出更加优雅和可维护的代码。
相关文章:
深入理解代理模式(Proxy):静态代理、动态代理与AOP
目录 1. 代理模式简介2. 静态代理3. 动态代理 3.1 JDK动态代理3.2 CGLIB动态代理 4. 面向切面编程(AOP)5. 实战示例6. 总结与最佳实践 1. 代理模式简介 代理模式是一种结构型设计模式,它允许我们提供一个代理来控制对其他对象的访问。代理模式在不改变原始类代码…...
项目中如何选择JVM垃圾回收器?
大家好,我是锋哥。今天分享关于【项目中如何选择JVM垃圾回收器?】面试题。希望对大家有帮助; 项目中如何选择JVM垃圾回收器? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在一个项目中选择合适的 JVM 垃圾回收器 (Garbage Collector, GC…...
如何借助5G网关实现油罐车安全在线监测
油罐车是常见的特种运输车辆,用以运送各种汽油、柴油、原油等油品,运输危险系数大,而且由于油罐车需要经常行驶在城区道路,为城市各个加油站点、企业工厂运输补充所需油料,因此也是危化品运输车辆的重点监测和管控对象…...
Edge SCDN的独特优势有哪些?
强大的边缘计算能力 Edge SCDN(边缘安全加速)是酷盾安全推出的边缘集分布式 DDoS 防护、CC 防护、WAF 防护、BOT 行为分析为一体的安全加速解决方案。通过边缘缓存技术,智能调度使用户就近获取所需内容,为用户提供稳定快速的访问…...
在Goland中对goroutine协程断点调试
在Goland中对goroutine协程断点调试 环境: Goland 参考了 chatgpt 的回复 进行断点调试的代码 package mainimport ("fmt""sync""time" )// worker 模拟处理任务 func worker(id int, wg *sync.WaitGroup) {defer wg.Done() // 确保任务完成后…...
解密分布式锁:保障系统一致性的关键
作者:后端小肥肠 🍇 我写过的文章中的相关代码放到了gitee,地址:xfc-fdw-cloud: 公共解决方案 🍊 有疑问可私信或评论区联系我。 🥑 创作不易未经允许严禁转载。 目录 1. 前言 2. 为何要使用分布式锁&…...
Redis 击穿、穿透与雪崩:深度解析与应对策略
在使用 Redis 作为缓存的系统架构中,缓存击穿、穿透和雪崩是三个常见且可能对系统性能产生严重影响的问题。深入理解这些问题并掌握有效的应对策略对于构建稳定、高效的系统至关重要。 一、缓存击穿 (一)问题描述 缓存击穿是指一个热点 key…...
8086处理器的寻址方式
概念 在计算机系统中,处理器操作和处理的是数值,那么,必定涉及数值从哪里来,处理后送到哪里去,这称为寻址方式(Addressing Mode)。 简单地说,寻址方式就是如何找到要操作的数据,以及如何找到存…...
Mask实现裁剪的原理浅析
简单来说,就是Mask会设置继承了MaskableGraphic的组件的Shader属性,进行特定的模板测试 一张普通的Image,当不挂Mask组件时,其默认Shader的模板缓存属性是这样的 当挂载上Mask时,会改变 Stencil ID变成了1ÿ…...
每隔一秒单片机向电脑发送一个16进制递增数据
SCON0x50 SM00 SM11(工作方式为方式一) REN1允许单片机从电脑接收数据 TB8 RB8 SM2是方式2和方式3直接配置为0 TI为发送中断请求标志位 由硬件配置为1 必须由 软件复位为0,RI为接收中断请求标志位,同理TI UART.c #include &l…...
逆向攻防世界CTF系列56-easy_Maze
逆向攻防世界CTF系列56-easy_Maze 64位无壳,看题目就知道是迷宫问题了 int __fastcall main(int argc, const char **argv, const char **envp){__int64 v3; // raxint v5[7][7]; // [rsp0h] [rbp-270h] BYREFint v6[104]; // [rspD0h] [rbp-1A0h] BYREFv6[52] 1…...
【Linux网络编程】应用层:HTTP协议 | URL | 简单实现一个HTTP服务器 | 永久重定向与临时重定向
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站 🌈个人主页: 南桥几晴秋 🌈C专栏: 南桥谈C 🌈C语言专栏: C语言学习系…...
电压调整电路汇总
目录: 一、LDO 1、LM1117 2、NCV33275 3、TLE42764 4、TPS7B67xx-Q1 5、总结 二、DCDC转换器 1、LM2576与LM2596 2、MC34063 一、LDO 1、LM1117 LM1117 是一款在 800mA 负载电流下具有 1.2V 压降的低压降稳压器。 LM1117 提供可调节电压版本,…...
day28 文件IO及进程线程基础
讨论光标共享情况 1.dup和dup2定义变量赋值都共享光标 2.使用两个描述符调用两次open函数打开同一个文件,不共享光标 #include <myhead.h>int main(int argc, const char *argv[]) {//1、描述符赋值给新的变量char buff[1024] "abcdefg";int ne…...
【Azure 架构师学习笔记】- Azure Function (1) --环境搭建和背景介绍
本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Function 】系列。 前言 随着无服务计算的兴起和大数据环境中的数据集成需求, 需要使用某些轻量级的服务,来实现一些简单操作。因此Azure Function就成了微软云上的一个必不可少的组成部分。 …...
前端文件下载
这里写自定义目录标题 前端文件下载方法使用a标签使用iframe标签二进制流 前端文件下载方法 使用a标签 /*** 文件下载方法 使用a标签* 存在浏览器下载时,太快的话,会取消上次的下载请求* param {*} href* param {*} filename*/ export function downlo…...
前端成长之路:HTML(3)
在HTML中,有列表标签。列表最大的特点是整齐、简洁、有序,用列表进行布局会更加自由方便。根据使用的情景不同,可以将列表分为三大类:无序列表、有序列表和自定义列表。 无序列表 在HTML中使用<ul>标签定义一个无序列表&a…...
无人机自动机库的功能与作用!
一、无人机自动机库的功能 智能停放与管理 无人机自动机库为无人机提供了一个安全、可靠的停放环境。通过先进的感知技术和安全防护措施,它能够实时监测周围环境,确保无人机免受恶劣天气或潜在风险的侵害。 无人机在机库内可以实现智能停放࿰…...
ubuntu 新建脚本shell并增加图标 双击应用实现python运行
1.使用nano创建shell脚本文件 需要在终端窗口中输入“nano”以打开文本编辑器。 nano 在创建脚本文件前,我们要了解脚本文件是如何运行的: 直接运行:直接在终端直接输入需要运行的脚本文件名称,系统或用缺省版本的shell运行脚…...
ANR 分析SOP
遇到ANR问题不要慌,大部分情况下可能是系统or测试手段问题,我们按照如下关键字排查定位 文章目录 1 是否是 heapdump 导致?1.1 dump开始1.2 dump结束 1 是否是 heapdump 导致? 使用 hprof: heap dump 关键词过滤,在d…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...
【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》
近日,嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,海云安高敏捷信创白盒(SCAP)成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解…...
