深入理解代理模式(Proxy):静态代理、动态代理与AOP

目录

  • 1. 代理模式简介
  • 2. 静态代理
  • 3. 动态代理
    • 3.1 JDK动态代理
    • 3.2 CGLIB动态代理
  • 4. 面向切面编程(AOP)
  • 5. 实战示例
  • 6. 总结与最佳实践

1. 代理模式简介

代理模式是一种结构型设计模式,它允许我们提供一个代理来控制对其他对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。

代理模式的UML类图

«interface»
Subject
+request()
RealSubject
+request()
Proxy
-realSubject: RealSubject
+request()

代理模式的主要角色

  • 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. 总结与最佳实践

代理模式是一种非常实用的设计模式,它可以帮助我们在不修改原有代码的情况下,添加新的功能:

  1. 静态代理
    • 简单直观,但是代理类需要手动编写
    • 适用于代理类较少的场景
  2. JDK动态代理
    • 只能代理实现了接口的类
    • 运行时动态生成代理类
    • 灵活性高
  3. CGLIB动态代理
    • 可以代理未实现接口的类
    • 通过继承实现
    • 性能较好
  4. Spring AOP
    • 提供了更高层次的抽象
    • 支持声明式代理
    • 结合了动态代理的优点

使用建议

  1. 如果目标类有接口,优先使用JDK动态代理
  2. 如果目标类没有接口,使用CGLIB动态代理
  3. 在Spring框架中,优先使用Spring AOP,它能自动选择合适的代理方式

注意事项

  1. 代理模式会增加系统的复杂度
  2. 动态代理可能会带来一定的性能开销
  3. 使用CGLib时注意final方法无法被代理
  4. 在使用Spring AOP时要注意代理对象的作用域

代理模式是Java开发中非常重要的一个设计模式,掌握好它可以帮助我们写出更加优雅和可维护的代码。

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

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

相关文章

java+springboot+mysql私人会所管理系统

项目介绍: 使用javaspringbootmysql开发的私人会所管理系统,系统包含管理员、技师、用户角色,功能如下: 管理员:用户管理;服务项目;技师管理;房间管理;预约管理&#x…

Formality:set_svf命令

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 svf文件的全称是Setup Verification for Formality,即Design Compiler提供给Formality的设置验证文件,它的作用是为Formality的指导模式(Gui…

Hive 数据操作语言全面解析

Hive 数据操作语言全面解析 在 Hive 大数据处理框架中,数据操作语言(DML)提供了多种方式来操作和修改数据,包括数据的加载、插入、更新、删除以及合并等操作。本文将详细介绍 Hive 中各类数据操作语句的语法、用法、注意事项以及…

JS API日期对象

目标:掌握日期对象,可以让网页显示日期 日期对象:用来表示时间的对象 作用:可以得到当前系统时间 实例化 目标:能够实现实例化日期对象 在代码中发现了new关键字时,一般将这个操作称为实例化 创建一个时…

【前端】JavaScript中的闭包与垃圾回收机制详解

博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 💯前言💯垃圾回收机制(Garbage Collection, GC)垃圾回收的核心原理核心过程 函数作用域与垃圾回收运行分析输出结果 垃圾回收的局限性与挑战 &#x1f4a…

单臂路由配置

知识点 单臂路由指在路由器上的一个接口配置子接口(逻辑接口)来实现不同vlan间通信 路由器上的每个物理接口都可以配置多个子接口(逻辑接口) 公司的财务部、技术部和业务部有多台计算机,它们使用一台二层交换机进行互…

verilog编程规范

verilog编程规范 文章目录 verilog编程规范前言一、代码划分二、verilog编码ABCDEFG 前言 高内聚,低耦合,干净清爽的代码 一、代码划分 高内聚: 一个功能一个模块干净的接口提取公共的代码 低耦合: 模块之间低耦合尽量用少量…

WEB安全基础知识

WAF全称为Web Application Firewall(网页应用防火墙)是一种专门设计用来保护web应用免受各种网络攻击的安全防护措施。它位于客户端与服务器之间,监控和过滤HTTP流量,从而拦截恶意请求、识别并防御常见的web攻击。 WAF的主要功能…

qemu安装arm64架构银河麒麟

qemu虚拟化软件,可以在一个平台上模拟另一个硬件平台,可以支持多种处理器架构。 一、安装 安装教程:https://blog.csdn.net/qq_36035382/article/details/125308044 下载链接:https://qemu.weilnetz.de/w64/2024/ 我下载的是 …

前端怎么用 EventSource?EventSource 怎么配置请求头及加参数?EventSourcePolyfill 使用方法

前言 在前端开发中,特别是实时数据更新的场景下,EventSource 是一个非常实用的 API。它允许浏览器与服务器建立单向连接,服务器可以持续地发送数据给客户端,而无需客户端不断轮询。本文将详细介绍 EventSource 的使用方法、如何配…

188-下翻便携式6U CPCI工控机箱

一、板卡概述 下翻式CPCI便携工控机,系统采用6u cpci背板结构,1个系统槽,7个扩展槽, 满足对携带的需求,可装标准6U8槽CPCI主板,8个扩展槽, 满足客户对空间扩展的需求.可宽温服务的工作产品,15高亮度液晶显示屏,超薄88键笔记本键盘,触摸式鼠标,加固型机箱结构,使它能够适应各种复…

网页核心页面设计(第9章)

一、多个边框阴影 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-…

SpringBoot中Selenium详解

文章目录 SpringBoot中Selenium详解一、引言二、集成Selenium1、环境准备1.1、添加依赖 2、编写测试代码2.1、测试主类2.2、页面对象2.3、搜索组件 三、使用示例四、总结 SpringBoot中Selenium详解 一、引言 在现代软件开发中&#xff0c;自动化测试是提高软件质量、减少重复…

Edge SCDN的独特优势有哪些?

强大的边缘计算能力 Edge SCDN&#xff08;边缘安全加速&#xff09;是酷盾安全推出的边缘集分布式 DDoS 防护、CC 防护、WAF 防护、BOT 行为分析为一体的安全加速解决方案。通过边缘缓存技术&#xff0c;智能调度使用户就近获取所需内容&#xff0c;为用户提供稳定快速的访问…

Fastapi教程:使用aioredis异步访问redis

本文将介绍如何使用 FastAPI 异步访问 Redis&#xff0c;包括环境配置、连接创建、数据库初始化、增删查改操作、键过期、管道&#xff08;pipeline&#xff09;操作以及事务管理等内容。 环境准备 首先&#xff0c;我们需要安装必要的依赖包。Redis 是一个键值存储系统&…

duxapp 2024-12-09 更新 PullView可以弹出到中间,优化CLI使用体验

UI库 修复 Button 禁用状态失效的问题Modal 组件即将停用&#xff0c;请使用 PullView 基础库 PullView side 新增 center 指定弹出到屏幕中间PullView 新增 duration 属性&#xff0c;指定动画时长新增 useBackHandler hook 用来阻止安卓端点击返回键 RN端 修复 windows …

多线程与线程互斥

目录 引言 一、多线程设计 多线程模拟抢票 二、互斥锁 互斥量的接口 修改抢票代码 锁的原理 锁的封装&#xff1a;RAII 引言 随着信息技术的飞速发展&#xff0c;计算机软件正变得越来越复杂&#xff0c;对性能和响应速度的要求也日益提高。在这样的背景下&#xff0c;…

Vue导出报表功能【动态表头+动态列】

安装依赖包 npm install -S file-saver npm install -S xlsx npm install -D script-loader创建export-excel.vue组件 代码内容如下&#xff08;以element-ui样式代码示例&#xff09;&#xff1a; <template><el-button type"primary" click"Expor…

ZUC256 Go Go Go!!!

文章目录 背景运行效果代码 背景 因业务需要使用ZUC算法&#xff0c;GitHub上又没有对ZUC256相对应的Go语言的实现。 吃水不忘挖井人&#xff0c;在这里感谢GmSSL及BouncyCastle两个强大的密码学库&#xff01; 本ZUC256的编写&#xff0c;参考了这两个库及中科院软件院发布的…

力扣打卡12:复原IP地址

链接&#xff1a;93. 复原 IP 地址 - 力扣&#xff08;LeetCode&#xff09; 这道题需要对字符串进行操作&#xff0c;我选择了三层循环&#xff0c;其实还可以递归。 我在循环时进行了剪枝&#xff0c;比如一些情况直接跳出循环。 我的代码&#xff1a; class Solution { p…