当前位置: 首页 > news >正文

深入理解代理模式(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开发中非常重要的一个设计模式,掌握好它可以帮助我们写出更加优雅和可维护的代码。

相关文章:

深入理解代理模式(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&#xff…...

每隔一秒单片机向电脑发送一个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 提供可调节电压版本&#xff0c…...

day28 文件IO及进程线程基础

讨论光标共享情况 1.dup和dup2定义变量赋值都共享光标 2.使用两个描述符调用两次open函数打开同一个文件&#xff0c;不共享光标 #include <myhead.h>int main(int argc, const char *argv[]) {//1、描述符赋值给新的变量char buff[1024] "abcdefg";int ne…...

【Azure 架构师学习笔记】- Azure Function (1) --环境搭建和背景介绍

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Function 】系列。 前言 随着无服务计算的兴起和大数据环境中的数据集成需求&#xff0c; 需要使用某些轻量级的服务&#xff0c;来实现一些简单操作。因此Azure Function就成了微软云上的一个必不可少的组成部分。 …...

前端文件下载

这里写自定义目录标题 前端文件下载方法使用a标签使用iframe标签二进制流 前端文件下载方法 使用a标签 /*** 文件下载方法 使用a标签* 存在浏览器下载时&#xff0c;太快的话&#xff0c;会取消上次的下载请求* param {*} href* param {*} filename*/ export function downlo…...

前端成长之路:HTML(3)

在HTML中&#xff0c;有列表标签。列表最大的特点是整齐、简洁、有序&#xff0c;用列表进行布局会更加自由方便。根据使用的情景不同&#xff0c;可以将列表分为三大类&#xff1a;无序列表、有序列表和自定义列表。 无序列表 在HTML中使用<ul>标签定义一个无序列表&a…...

无人机自动机库的功能与作用!

一、无人机自动机库的功能 智能停放与管理 无人机自动机库为无人机提供了一个安全、可靠的停放环境。通过先进的感知技术和安全防护措施&#xff0c;它能够实时监测周围环境&#xff0c;确保无人机免受恶劣天气或潜在风险的侵害。 无人机在机库内可以实现智能停放&#xff0…...

ubuntu 新建脚本shell并增加图标 双击应用实现python运行

1.使用nano创建shell脚本文件 需要在终端窗口中输入“nano”以打开文本编辑器。 nano 在创建脚本文件前&#xff0c;我们要了解脚本文件是如何运行的&#xff1a; 直接运行&#xff1a;直接在终端直接输入需要运行的脚本文件名称&#xff0c;系统或用缺省版本的shell运行脚…...

ANR 分析SOP

遇到ANR问题不要慌&#xff0c;大部分情况下可能是系统or测试手段问题&#xff0c;我们按照如下关键字排查定位 文章目录 1 是否是 heapdump 导致&#xff1f;1.1 dump开始1.2 dump结束 1 是否是 heapdump 导致&#xff1f; 使用 hprof: heap dump 关键词过滤&#xff0c;在d…...

Dify向量检索精度跃升47%的秘密(重排序Pipeline低延迟部署避坑手册)

第一章&#xff1a;Dify重排序算法精度跃升47%的核心动因解析Dify v0.12.0 引入的重排序&#xff08;Reranking&#xff09;模块并非简单叠加模型&#xff0c;而是通过三重协同优化机制实现精度质变。核心突破在于将传统单阶段打分范式升级为「语义对齐—上下文感知—动态归一化…...

IT 流程越来越完整,但管理反而变得更难了

在很多企业的 IT 管理过程中&#xff0c;一个非常明显的趋势是&#xff1a;流程在不断增加。 从最初的简单问题处理&#xff0c;到后来的事件管理、问题管理、变更管理&#xff0c;再到审批流程、发布流程&#xff0c;各类流程逐渐被建立起来。从管理角度看&#xff0c;这是一种…...

Llama-3.2V-11B-cot部署详解:自动修复视觉权重加载致命Bug全过程

Llama-3.2V-11B-cot部署详解&#xff1a;自动修复视觉权重加载致命Bug全过程 1. 项目概述 Llama-3.2V-11B-cot是基于Meta Llama-3.2V-11B-cot多模态大模型开发的高性能视觉推理工具&#xff0c;专为双卡RTX 4090环境深度优化。本工具通过自动修复视觉权重加载等核心Bug&#…...

颠覆式突破:无需模拟器,在Windows系统上直接运行Android应用的革命性方案

颠覆式突破&#xff1a;无需模拟器&#xff0c;在Windows系统上直接运行Android应用的革命性方案 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 想象一下&#xff0c;…...

英雄联盟智能工具集:3个颠覆性功能重塑你的游戏体验

英雄联盟智能工具集&#xff1a;3个颠覆性功能重塑你的游戏体验 【免费下载链接】League-Toolkit 兴趣使然的、简单易用的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 作为英雄联盟玩家…...

w3x2lni:魔兽地图跨版本转换的技术突破与实践指南

w3x2lni&#xff1a;魔兽地图跨版本转换的技术突破与实践指南 【免费下载链接】w3x2lni 魔兽地图格式转换工具 项目地址: https://gitcode.com/gh_mirrors/w3/w3x2lni 问题引入&#xff1a;版本壁垒下的魔兽地图开发困境 在魔兽争霸III的地图开发领域&#xff0c;版本迭…...

专业流媒体视频下载工具技术解析与使用指南

专业流媒体视频下载工具技术解析与使用指南 价值主张&#xff1a;高效解决流媒体内容本地化需求 在数字内容消费日益普及的今天&#xff0c;用户对在线视频资源的本地保存需求持续增长。m3u8-downloader作为一款专业的流媒体下载工具&#xff0c;专注于解决m3u8格式视频的高效…...

Python实战:温度转换小工具开发(附GESP考试真题解析)

Python实战&#xff1a;温度转换小工具开发与GESP考试技巧精讲 温度转换是编程入门阶段的经典案例&#xff0c;也是GESP考试中常见的题型。本文将从零开始构建一个功能完整的温度转换工具&#xff0c;同时深入解析GESP考试中可能遇到的类似题型&#xff0c;帮助初学者掌握Pytho…...

OpenClaw + 搜索与资讯:让 AI 帮你「刷」信息,告别信息焦虑

你每天花多少时间刷信息流&#xff1f;30分钟&#xff1f;1小时&#xff1f;今天这篇文章&#xff0c;帮你把这段时间降为零。 01 信息过载是现代人的标配焦虑 早上醒来第一件事是什么&#xff1f;很多人已经条件反射地拿起手机&#xff0c;打开微信公众号、知乎、微博、Twitt…...

CentOS 8下openLDAP服务器搭建避坑指南:从第三方仓库到phpLDAPadmin配置

CentOS 8企业级openLDAP部署实战&#xff1a;从仓库选择到安全加固全解析 在当今企业IT架构中&#xff0c;目录服务作为身份认证和资源管理的核心组件&#xff0c;其重要性不言而喻。而openLDAP作为开源目录服务的标杆解决方案&#xff0c;凭借其轻量高效、跨平台兼容的特性&am…...