深入理解 Java 代理模式:从基础到实战​

       在软件开发的世界里,设计模式是程序员智慧的结晶,它们为解决常见问题提供了经过验证的最佳实践。代理模式作为一种常用的结构型设计模式,在 Java 开发中有着广泛的应用。本文将深入探讨 Java 代理模式,从基本概念、分类、实现原理到实际应用,带您全面理解这一重要的设计模式。​

一、代理模式基本概念​

代理模式(Proxy Pattern)的核心思想是通过引入一个代理对象来控制对真实对象的访问。代理对象在客户端和真实对象之间起到中介作用,它可以在不修改真实对象代码的情况下,为真实对象添加额外的功能或控制访问行为。​

举个生活中的例子,比如你想租房子,可能会通过房产中介来寻找合适的房源。这里的房产中介就是一个代理,它代表你与房东进行沟通,处理租房过程中的各种事务。你不需要直接与所有房东打交道,只需通过中介即可,中介可以在这个过程中提供​

筛选房源安排看房等额外服务。在软件开发中,代理模式的作用类似,它可以帮助我们更好地管理对真实对象的访问,实现功能的扩展和优化。​

代理模式涉及三个主要角色:​

  1. 抽象主题(Subject):定义了真实主题和代理主题的共同接口,客户端通过该接口访问真实主题和代理主题。​
  2. 真实主题(Real Subject):实际处理业务逻辑的对象,是代理主题所代表的真实对象。、​
  3. 代理主题(Proxy):实现了抽象主题接口,持有对真实主题的引用,负责控制对真实主题的访问,并可以在访问前后添加额外的操作。​

二、代理模式的分类​

在 Java 中,代理模式主要分为静态代理和动态代理两种类型,其中动态代理又包括 JDK 动态代理和 CGLIB 动态代理。​

(一)静态代理​

静态代理是指在编译时就已经确定了代理类的代码,代理类和真实类在编译时是一一对应的。下面通过一个简单的例子来演示静态代理的实现。​

假设我们有一个接口Subject,定义了一个请求方法request():​

public interface Subject {​

void request();​

}​

真实主题类RealSubject实现了该接口,实现具体的请求逻辑:​

public class RealSubject implements Subject {​

@Override​

public void request() {​

System.out.println("RealSubject handling request");​

}​

}​

代理类ProxySubject也实现了Subject接口,并且持有一个RealSubject对象的引用。在代理类的request()方法中,首先可以执行一些前置处理,然后调用真实主题的request()方法,最后执行一些后置处理:​

public class ProxySubject implements Subject {​

private RealSubject realSubject;​

public ProxySubject(RealSubject realSubject) {​

this.realSubject = realSubject;​

}​

@Override​

public void request() {​

System.out.println("Proxy before request");​

realSubject.request();​

System.out.println("Proxy after request");​

}​

}​

客户端使用代理类来访问真实主题:​

public class Client {​

public static void main(String[] args) {​

RealSubject realSubject = new RealSubject();​

ProxySubject proxySubject = new ProxySubject(realSubject);​

proxySubject.request();​

}​

}​

运行结果为:​

Proxy before request​

RealSubject handling request​

Proxy after request​

静态代理的优点是实现简单,容易理解,在编译时就可以确定代理关系。但它的缺点也很明显,当需要代理多个不同的真实主题时,需要为每个真实主题创建对应的代理类,这会导致代码量增加,灵活性较差。​

(二)动态代理​

动态代理是在运行时动态生成代理类的一种方式,它不需要在编译时就确定代理类的代码,而是通过反射机制在运行时生成代理类。动态代理可以分为 JDK 动态代理和 CGLIB 动态代理。​

1. JDK 动态代理​

JDK 动态代理是 Java 自带的动态代理实现,它基于接口实现代理。JDK 动态代理需要使用java.lang.reflect.Proxy类和InvocationHandler接口。​

InvocationHandler接口定义了一个invoke方法,当调用代理对象的方法时,会调用该invoke方法。我们需要实现InvocationHandler接口,在invoke方法中处理对真实主题的调用和额外的逻辑。​

下面以之前的Subject接口和RealSubject类为例,演示 JDK 动态代理的实现。​

首先,创建一个InvocationHandler实现类SubjectInvocationHandler:​

import java.lang.reflect.InvocationHandler;​

import java.lang.reflect.Method;​

public class SubjectInvocationHandler implements InvocationHandler {​

private Object target;​

public SubjectInvocationHandler(Object target) {​

this.target = target;​

}​

@Override​

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {​

System.out.println("JDK Proxy before method");​

Object result = method.invoke(target, args);​

System.out.println("JDK Proxy after method");​

return result;​

}​

}​

然后,在客户端中使用Proxy.newProxyInstance方法生成代理对象:​

import java.lang.reflect.Proxy;​

public class Client {​

public static void main(String[] args) {​

RealSubject realSubject = new RealSubject();​

SubjectInvocationHandler handler = new SubjectInvocationHandler(realSubject);​

Subject proxy = (Subject) Proxy.newProxyInstance(​

realSubject.getClass().getClassLoader(),​

realSubject.getClass().getInterfaces(),​

handler​

);​

proxy.request();​

}​

}​

运行结果与静态代理类似:​

JDK Proxy before method​

RealSubject handling request​

JDK Proxy after method​

JDK 动态代理的优点是无需引入第三方库,基于接口实现,符合面向接口编程的思想。但它的缺点是只能代理实现了接口的类,对于没有接口的类无法使用。​

2. CGLIB 动态代理​

CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它可以在运行时动态生成子类来实现代理。CGLIB 动态代理不需要依赖接口,它通过继承真实类来生成代理类,因此可以代理没有接口的类。​

使用 CGLIB 动态代理需要引入 CGLIB 的依赖,在 Maven 项目中可以添加以下依赖:​

<dependency>​

<groupId>cglib</groupId>​

<artifactId>cglib</artifactId>​

<version>3.3.0</version>​

</dependency>​

假设我们有一个没有实现接口的真实类RealSubjectNoInterface:​

public class RealSubjectNoInterface {​

public void request() {​

System.out.println("RealSubjectNoInterface handling request");​

}​

}​

创建一个 CGLIB 的代理类,需要实现MethodInterceptor接口,该接口的intercept方法会在调用代理对象的方法时被触发:​

import net.sf.cglib.proxy.MethodInterceptor;​

import net.sf.cglib.proxy.MethodProxy;​

import java.lang.reflect.Method;​

public class CglibProxy implements MethodInterceptor {​

private Object target;​

public CglibProxy(Object target) {​

this.target = target;​

}​

public Object getProxy() {​

net.sf.cglib.proxy.Enhancer enhancer = new net.sf.cglib.proxy.Enhancer();​

enhancer.setSuperclass(target.getClass());​

enhancer.setCallback(this);​

return enhancer.create();​

}​

@Override​

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {​

System.out.println("CGLIB Proxy before method");​

Object result = method.invoke(target, args);​

System.out.println("CGLIB Proxy after method");​

return result;​

}​

}​

客户端使用 CGLIB 代理:​

public class Client {​

public static void main(String[] args) {​

RealSubjectNoInterface realSubject = new RealSubjectNoInterface();​

CglibProxy cglibProxy = new CglibProxy(realSubject);​

RealSubjectNoInterface proxy = (RealSubjectNoInterface) cglibProxy.getProxy();​

proxy.request();​

}​

}​

运行结果为:​

CGLIB Proxy before method​

RealSubjectNoInterface handling request​

CGLIB Proxy after method​

CGLIB 动态代理的优点是无需接口,代理类是真实类的子类,可以代理任何类(除了 final 类)。但由于是通过继承实现的,所以不能代理 final 类,并且性能上可能会有一定的开销。​

(三)JDK 动态代理与 CGLIB 动态代理的对比​

特性​

JDK 动态代理​

CGLIB 动态代理​

依赖​

无需第三方库,Java 自带​

需要引入 CGLIB 库​

代理方式​

基于接口​

基于子类(继承)​

适用场景​

代理实现了接口的类​

代理没有接口的类,或者希望更灵活的代理方式​

性能​

反射调用,性能稍低​

生成子类,性能相对较高​

三、代理模式的应用场景​

代理模式在 Java 开发中有很多实际应用场景,以下是一些常见的例子:​

(一)远程代理​

远程代理用于代理访问远程服务器上的对象。例如,在分布式系统中,客户端需要访问远程服务器上的服务,远程代理可以隐藏网络通信的细节,让客户端像调用本地对象一样调用远程对象的方法。RMI(Remote Method Invocation)就是一种典型的远程代理应用。​

(二)虚拟代理​

虚拟代理用于延迟加载真实对象,只有在需要使用真实对象时才创建它。例如,当处理大对象或需要复杂初始化的对象时,虚拟代理可以在对象被使用前先返回一个简单的代理对象,当真正需要使用时再加载真实对象,从而提高系统的性能和响应速度。​

(三)保护代理​

保护代理用于控制对真实对象的访问权限,确保只有符合条件的客户端才能访问真实对象。例如,在系统中,不同的用户角色可能具有不同的访问权限,保护代理可以在代理对象中检查客户端的权限,决定是否允许访问真实对象。​

(四)日志记录与性能监控​

代理模式可以用于在方法调用前后添加日志记录和性能监控代码,而无需修改真实对象的代码。通过代理对象,我们可以统一在代理类中实现日志记录和性能统计功能,实现代码的复用和解耦。​

四、代理模式的优缺点​

(一)优点​

  1. 解耦:代理模式将客户端与真实对象解耦,客户端通过代理对象访问真实对象,不需要知道真实对象的具体实现细节。​
  2. 扩展性:可以在不修改真实对象代码的情况下,通过代理对象添加额外的功能,符合开闭原则。​
  3. 控制访问:代理对象可以控制对真实对象的访问,实现访问权限控制、延迟加载等功能。​
  4. 集中处理公共逻辑:像日志记录、事务处理、性能监控等公共逻辑可以集中在代理类中实现,避免在真实对象中重复编写代码。​

(二)缺点​

  1. 增加复杂度:引入代理模式会增加系统的类数量和复杂度,特别是在使用动态代理时,需要理解反射机制和相关的 API。​
  2. 性能开销:动态代理通过反射机制调用方法,会带来一定的性能开销;静态代理则需要为每个真实对象创建代理类,增加了代码量。​
  3. 学习成本:对于初学者来说,理解代理模式的原理和实现需要一定的学习成本,特别是动态代理涉及到反射等高级特性。​

五、总结​

代理模式是一种非常实用的设计模式,它通过引入代理对象来控制对真实对象的访问,实现了功能的扩展和优化。在 Java 中,静态代理实现简单,但灵活性较差;动态代理包括 JDK 动态代理和 CGLIB 动态代理,分别适用于不同的场景。JDK 动态代理基于接口,CGLIB 动态代理基于子类,开发者可以根据具体需求选择合适的代理方式。​

代理模式在实际开发中有广泛的应用,如远程代理、虚拟代理、保护代理等,它能够帮助我们更好地管理对象的访问,实现代码的解耦和复用。虽然代理模式存在一定的缺点,但在合适的场景下使用,可以显著提高系统的可维护性和扩展性。​

作为 Java 开发者,掌握代理模式的原理和应用是非常重要的。通过不断地实践和学习,我们可以更好地运用代理模式来解决实际开发中的问题,写出更加优雅、高效的代码。​

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

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

相关文章

android-ndk开发(11): 安装 repo 命令

1. 长话短说 mkdir ~/soft/bin curl -L https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o repo chmod x repo~/.pathrc 添加: export PATH$PATH:~/soft/bin2. 短话长说 repo 的官方介绍页面&#xff1a; https://gerrit.googlesource.com/git-repo/ repo 的官方下载地…

45.传导发射整改摸底测试方法

传导发射整改摸底测试方法 1. 正式摸底预测试2. 简易方法预测试3. 分析频谱4. 探查传播路径5. 施加措施 1. 正式摸底预测试 去正式实验室做一次预测试&#xff0c;取得频谱图&#xff0c;确定超标频点和超标量&#xff08;备用&#xff09;。 2. 简易方法预测试 用小电流探头…

【漫话机器学习系列】249.Word2Vec自然语言训练模型

【自然语言处理】用 Word2Vec 将词语映射到向量空间详解 一、背景介绍 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;我们常常需要将文本信息转化为机器能够理解和处理的形式。传统的方法&#xff0c;如 one-hot编码&#xff0c;虽然简单&#xff0c;但存在严重…

支付宝 SEO 优化:提升小程序曝光与流量的完整指南

在拥有庞大用户基数的支付宝平台上&#xff0c;小程序已成为商家触达用户、提供服务的重要渠道。然而&#xff0c;随着平台上小程序数量的快速增长&#xff0c;如何在激烈的竞争中脱颖而出&#xff0c;获得更多的曝光和流量&#xff0c;成为每个开发者和运营者必须面对的关键挑…

20250509——TOPSIS计算各方案得分

目录 TOPSIS原理 算例分析 参考文献 逼近理想解排序法&#xff08;Technique for Order Preference by Similarity to an Ideal Solution, TOPSIS&#xff09;是一种多属性决策分析工具&#xff0c;其核心在于通过衡量备选方案与理想目标的接近程度实现科学排序。该方法首先…

【QT】项目打包与发布安装

文章目录 一、引言二、前期准备三、打包QT项目3.1 编译release版3.2 相关依赖打包3.3 打包发布3.4 安装 四、打包普通项目五、结束语 一、引言 当Qt程序编写完成通过IDE编译就可以得到对应的可执行程序&#xff0c;这个可执行程序在本地运行是完全没有问题的。因为在本地有Qt环…

提示词工程:通向AGI时代的人机交互艺术

‌引言&#xff1a;从基础到精通的提示词学习之旅‌ 欢迎来到 ‌"AGI时代核心技能"‌ 系列课程的第二模块——‌提示词工程‌。在这个模块中&#xff0c;我们将系统性地探索如何通过精心设计的提示词&#xff0c;释放大型语言模型的全部潜力&#xff0c;实现高效、精…

三款实用电脑工具

今天为大家精心推荐三款实用软件&#xff0c;分别是人声伴奏分离软件、文件夹迁移软件和文字转拼音软件。 第一款&#xff1a;NovaMSS NovaMSS是一款功能强大的人声伴奏分离软件&#xff0c;它提供社区版和专业版&#xff0c;社区版永久免费。 该软件能够一键提取人声、伴奏、…

CentOS 7 安装指定版本 Docker 及镜像加速/配置优化攻略

摘要 本文详述 CentOS 7 系统下安装指定版本 Docker &#xff0c;涵盖镜像加速配置&#xff08;实测最快&#xff09;、存储位置优化、日志轮转等核心配置。 文章目录 一、安装指定版本Docker1.1 卸载旧版本&#xff08;如有&#xff09;1.2 安装依赖包1.3 添加Docker仓库&…

达梦、PostgreSQL数据库讲json解析成临时表(json_table函数的使用)

官网json_table文档路由 路径表达式文档路由 背景&#xff1a;有一些动态表格&#xff0c;需要新建一张中间表&#xff0c;里边就两三个字段比如主表id,某某类型id,某某类型是否选中。对于这种表新建个表还得加各种创建人修改人版本号是否逻辑删除啥的字段太过麻烦。于是可以直…

【软件测试】测试用例的设计方法

目录 一、基于需求进行测试用例的设计 1.1 功能需求测试分析 二、黑盒测试用例设计方法 2.1 等价类划分法(解决穷举) 2.1.1 等价类设计步骤 2.1.2 等价类划分法案例 2.1.2.1 验证 QQ 账号的合法性 2.1.2.2 验证某城市电话号码的正确性 2.1.3 适用场景 2.2 边界值分析…

微服务系统设计

在设计微服务系统时&#xff0c;需要综合考虑架构、业务划分、通信方式、数据管理、安全性、运维等多个方面的问题。 一、微服务系统设计需考虑的问题 1. 服务划分 如何合理拆分服务&#xff0c;避免“微服务地狱”。拆分粒度不宜过小&#xff1a;太多服务增加管理和通信成本…

Django ORM: values() 和 annotate() 方法详解

1. values()方法 1.1 基本概念 values()方法用于返回一个包含字典的QuerySet&#xff0c;而不是模型实例。每个字典表示一个对象&#xff0c;键对应于模型字段名称。 1.2 基本用法 # 获取所有书籍的标题和出版日期 from myapp.models import Bookbooks Book.objects.value…

43.防雷击浪涌设计

防雷击浪涌设计 1. 雷击浪涌形成的机理2. 雷击浪涌的防护 1. 雷击浪涌形成的机理 雷击浪涌分为直击雷和感应雷&#xff1a; 直击雷——带电荷的云对建筑物、避雷针等放电&#xff0c;或者带异种电荷的雷云撞击放电&#xff0c;其能量超级大&#xff1b; 感应雷——雷云在高压…

双端口ram与真双端口ram的区别

端口独立性 真双端口RAM&#xff1a;拥有两个完全独立的读写端口&#xff08;Port A和Port B&#xff09;&#xff0c;每个端口都有自己的地址总线、数据总线、时钟、使能信号和写使能信号。这意味着两个端口可以同时进行读写操作&#xff0c;且互不干扰。 伪双端口RAM&…

Python毕业设计219—基于python+Django+vue的房屋租赁系统(源代码+数据库+万字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于pythonDjangovue的房屋租赁系统(源代码数据库万字论文)219 一、系统介绍 本项目前后端分离&#xff0c;分为租客、房东、管理员三种角色 1、租客&#xff1a; 注册、登录、公…

Webpack基本用法学习总结

Webpack 基本使用核心概念处理样式资源步骤&#xff1a; 处理图片资源修改图片输出文件目录 自动清空上次打包的内容EslintBabel处理HTML资源搭建开发服务器生产模式提取css文件为单独文件问题&#xff1a; Css压缩HTML压缩 小结1高级SourceMap开发模式生产模式 HMROneOfInclud…

数字IC前端学习笔记:锁存器的综合

相关阅读 数字IC前端专栏https://blog.csdn.net/weixin_45791458/category_12173698.html?spm1001.2014.3001.5482 锁存器是一种时序逻辑&#xff0c;与寄存器相比面积更小&#xff0c;但它的存在会使静态时序分析(STA)变得更加复杂&#xff0c;因此懂得什么样的设计会综合出…

LVGL源码学习之渲染、更新过程(2)---无效区域的处理

LVGL版本&#xff1a;8.1 往期回顾&#xff1a; LVGL源码学习之渲染、更新过程(1)---标记和激活 区域合并 在前面的代码分析中&#xff0c;发现标记无效区域的工作其实很繁琐&#xff0c;虽然大部分区域因为包含关系被剔除&#xff0c;但仍可能存在相互交叉的区域&#xff0c…

01 dnsmasq 中 dns服务

前言 这里我们主要是 来看一下 dns 服务器这边的相关业务处理 通常来说 在我们日常生活中 还是经常会需要使用 dns 的情况, 主要是更加友好的去给一个主机命名一个别名 比如 现在我的应用在服务器 192.168.220.133 但是我不想记这个生硬的 ip, 我可能更期望记录一个域名, …