Java-代理

在 Java 开发中,代理模式是一种非常重要的设计模式,它通过引入代理对象来控制对目标对象的访问,从而实现额外功能的增强。

一、代理模式的基本概念

代理模式的核心思想是:通过一个代理对象来间接访问目标对象,在不修改目标对象代码的前提下,为其增加额外功能。这种设计模式主要包含三个角色:

  • 抽象主题(Subject):定义代理类和目标类共同实现的接口,规定了要实现的业务方法
  • 目标对象(Real Subject):真正执行业务逻辑的类
  • 代理对象(Proxy):实现抽象主题接口,内部包含目标对象的引用,在调用目标方法前后可以添加额外操作

二、静态代理的实现与分析

静态代理是指在编译期就已经确定代理类的形式,代理类和目标类的关系在编译时就已固定。

1. 静态代理的代码实现

以服装工厂和动物工厂的代理为例,我们先定义抽象主题接口:

// 服装购买接口(抽象主题)
public interface ByClothes {public void clothes(String size);
}// 动物购买接口(抽象主题)
public interface ByAnimal {public void animals(String name);
}

然后实现目标对象类:

// 服装工厂(目标对象)
public class ClothesFactory implements ByClothes {@Overridepublic void clothes(String size) {System.out.println("生产尺寸为" + size + "的服装");}
}// 动物工厂(目标对象)
public class AnimalFactory implements ByAnimal {@Overridepublic void animals(String name) {System.out.println("生产名为" + name + "的小动物");}
}

最后创建静态代理类:

// 服装代理类
public class ProxyClothes implements ByClothes {// 持有目标对象引用public ClothesFactory factory = new ClothesFactory();@Overridepublic void clothes(String size) {System.out.println("售前服务");  // 额外功能factory.clothes(size);  // 调用目标方法System.out.println("售后服务");  // 额外功能}
}// 动物代理类
public class ProxyAnimal implements ByAnimal {// 持有目标对象引用public AnimalFactory animalFactory = new AnimalFactory();@Overridepublic void animals(String name) {System.out.println("售前处理");  // 额外功能animalFactory.animals(name);  // 调用目标方法System.out.println("售后处理");  // 额外功能}
}

2. 静态代理的优缺点

优点

  • 实现简单,易于理解
  • 在不修改目标对象的前提下扩展功能
  • 保护目标对象,降低直接访问风险

缺点

  • 代理类与目标类一一对应,接口变动时所有代理类都需修改
  • 代码冗余,每个目标类都需要创建对应的代理类
  • 编译期确定代理关系,灵活性差

三、动态代理的实现与分析

动态代理是指在程序运行时动态生成代理类,无需手动编写代理类代码,大大提高了代码的灵活性和可维护性。Java 通过java.lang.reflect包中的ProxyInvocationHandler实现动态代理。

1. 基于jdk的动态代理的代码实现

// 动态代理处理器
public class ProxyAll implements InvocationHandler {// 目标对象(可以是任何实现了接口的类)public Object factory;@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增强System.out.println("售前服务");// 调用目标对象的方法method.invoke(factory, args);// 后置增强System.out.println("售后服务");return null;}// 创建代理对象的方法public Object getProxyInstance() {// 参数说明:// 1. 目标对象的类加载器// 2. 目标对象实现的接口// 3. 当前InvocationHandler实例return Proxy.newProxyInstance(factory.getClass().getClassLoader(),factory.getClass().getInterfaces(),this);}
}

使用动态代理:

public class Test {public static void main(String[] args) {// 创建目标对象ClothesFactory clothesFactory = new ClothesFactory();AnimalFactory animalFactory = new AnimalFactory();// 创建动态代理处理器ProxyAll proxyAll = new ProxyAll();// 为服装工厂创建代理proxyAll.factory = clothesFactory;ByClothes clothesProxy = (ByClothes)proxyAll.getProxyInstance();clothesProxy.clothes("XL");  // 调用代理方法// 为动物工厂创建代理proxyAll.factory = animalFactory;ByAnimal animalProxy = (ByAnimal)proxyAll.getProxyInstance();animalProxy.animals("小鸭");  // 调用代理方法}
}

2. 基于jdk的动态代理的工作原理

  1. 通过Proxy.newProxyInstance()方法在运行时生成代理类的字节码
  2. 代理类实现了目标对象所实现的所有接口
  3. 当调用代理对象的方法时,会自动转发到InvocationHandlerinvoke()方法
  4. invoke()方法中实现对目标方法的增强(前置处理、后置处理等)

3. 基于jdk的动态代理的优缺点

优点

  • 无需手动编写代理类,减少代码冗余
  • 一个代理处理器可以代理多个目标对象,提高代码复用性
  • 运行时动态生成代理,灵活性高,易于扩展

缺点

  • 只能代理实现了接口的类
  • 实现相对复杂,理解难度较大
  • 性能略低于静态代理(因为涉及反射调用)

4. 基于CGLIB的动态代理

CGLIB 是一个第三方代码生成类库,需要引入依赖(以 Maven 为例):

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
1. 代码实现步骤
(1)定义目标类(无需实现接口)
package 代理;// 无需实现接口的目标类
public class FoodFactory {public void produce(String foodName) {System.out.println("生产食品:" + foodName);}
}
(2)实现 CGLIB 回调接口(MethodInterceptor)
package 代理;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;// CGLIB代理拦截器
public class CglibProxy implements MethodInterceptor {// 目标对象private Object target;public CglibProxy(Object target) {this.target = target;}// 生成代理对象public Object getProxyInstance() {// 1. 创建Enhancer对象(CGLIB的核心类)net.sf.cglib.proxy.Enhancer enhancer = new net.sf.cglib.proxy.Enhancer();// 2. 设置父类(目标类)enhancer.setSuperclass(target.getClass());// 3. 设置回调对象(当前拦截器)enhancer.setCallback(this);// 4. 生成代理子类并返回return enhancer.create();}// 拦截方法调用(类似JDK动态代理的invoke方法)@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 前置增强System.out.println("CGLIB-售前检查");// 调用目标方法(通过方法代理调用父类方法)Object result = method.invoke(target, args);// 后置增强System.out.println("CGLIB-售后保障");return result;}
}
(3)测试 CGLIB 代理
package 代理;public class CglibTest {public static void main(String[] args) {// 创建目标对象FoodFactory foodFactory = new FoodFactory();// 创建CGLIB代理对象FoodFactory proxy = (FoodFactory) new CglibProxy(foodFactory).getProxyInstance();// 调用代理方法proxy.produce("面包");}
}
(4)输出结果
CGLIB-售前检查
生产食品:面包
CGLIB-售后保障
2. CGLIB 动态代理的工作原理
  1. 继承机制:通过Enhancer类生成目标类的子类(代理类),并重写目标方法
  2. 方法拦截:当调用代理对象的方法时,会被MethodInterceptorintercept方法拦截
  3. 字节码生成:底层通过 ASM 框架直接操作字节码,动态生成代理类的 class 文件
3. CGLIB 的优缺点

优点

  • 无需目标类实现接口,适用范围更广
  • 基于继承实现,避免了 JDK 动态代理对接口的依赖
  • 性能通常优于 JDK 动态代理(因为避免了反射调用的部分开销)

缺点

  • 不能代理final类或final方法(因为无法继承)
  • 需要引入第三方依赖(JDK 动态代理是 JDK 自带的)
  • 生成代理类的过程比 JDK 动态代理更复杂

四、三种代理方式的全面对比

特性静态代理JDK 动态代理CGLIB 动态代理
代理原理手动编写代理类实现接口,持有目标对象引用运行时生成代理类实现目标接口,通过 InvocationHandler 增强运行时生成目标类的子类,通过 MethodInterceptor 增强
目标类要求需实现接口必须实现接口无需实现接口(但不能是 final 类)
代理类生成时机编译期运行期运行期
灵活性低(接口变动需修改代理类)中(可代理任意接口实现类)高(可代理任意非 final 类)
性能高(直接调用,无额外开销)中(反射调用有一定开销)高(字节码增强,接近直接调用)
依赖JDK 自带,无额外依赖JDK 自带(java.lang.reflect)需引入 CGLIB 和 ASM 依赖
适用场景接口稳定、代理类少的简单场景基于接口的代理(如 Spring AOP 默认选择)无接口的类代理(如 Spring 中对没有接口的 Bean 使用 CGLIB)
代码侵入性高(需手动编写代理类)低(无需编写代理类)低(无需编写代理类)

五、代理模式的应用场景

  1. 日志记录:在方法调用前后记录日志信息
  2. 权限控制:在调用目标方法前验证权限
  3. 性能监控:统计方法执行时间
  4. 事务管理:在数据库操作前后进行事务控制
  5. 缓存实现:对方法返回结果进行缓存处理
  6. 远程调用:如 RPC 框架中通过代理实现远程服务调用

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

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

相关文章

【基础知识】互斥锁、读写锁、自旋锁的区别

从定义、工作原理、适用场景和性能开销四个维度来剖析这三种锁的区别 核心结论 这三种锁的核心区别在于它们应对“锁已被占用”情况时的行为策略不同,而这直接决定了它们的性能和适用场景。 锁类型 核心策略 适用场景 互斥锁 (Mutex) 等不到,就睡 通用的独占访问,临界区执行…

智慧清洁革新者:有鹿机器人自述

晨曦微露&#xff0c;当城市还未完全苏醒&#xff0c;我已悄然完成数万平方米的清洁工作。作为有鹿智能巡扫机器人&#xff0c;我很荣幸能与您分享如何以科技之力重塑清洁行业的标准与体验。卓越技术&#xff1a;重新定义清洁新标准我搭载的聪明大脑是基于Master2000通用具身智…

python学习打卡day48

知识点回顾&#xff1a; 随机张量的生成&#xff1a;torch.randn函数卷积和池化的计算公式&#xff08;可以不掌握&#xff0c;会自动计算的&#xff09;pytorch的广播机制&#xff1a;加法和乘法的广播机制 ps&#xff1a;numpy运算也有类似的广播机制&#xff0c;基本一致 im…

记一次雪花算法 ID 精度丢失的Bug:前端接收到的 Long 被“四舍五入”了?

后端生成的 ID&#xff1a;1961005746230337538 前端收到的 ID&#xff1a;1961005746230337500 —— 少了 38&#xff1f;&#xff01;这不是 Bug&#xff0c;是 JavaScript 的“安全整数”陷阱&#xff01;本文记录一次真实项目中因 雪花算法 ID 精度丢失 导致的线上问题&…

零知开源——基于STM32F407VET6和ADXL345三轴加速度计的精准运动姿态检测系统

✔零知IDE 是一个真正属于国人自己的开源软件平台&#xff0c;在开发效率上超越了Arduino平台并且更加容易上手&#xff0c;大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码&#xff0c;让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品&am…

Android14 init.qcom.usb.rc详解

本文以高通平台为例&#xff0c;介绍其USB子系统启动以及USB各种配置动态切换的逻辑&#xff0c;它是以configfs架构实现动态配置USB。 相关文档 1. USB子系统的启动 1.1 on boot阶段 1.1.1 重启恢复用户选择的USB配置 当设备重启时恢复用户选择的USB配置&#xff0c;避免每…

Docker的常用命令及简单使用

1、docker的常用命令 1.1、帮助命令 docker version # 显示docker的版本信息 docker info # 显示docker的系统信息&#xff0c;包括镜像和容器的数量 docker 指令 --help # 查看某个指令的帮助命令可以通过docker --help查看docker常用命…

HGDB全文检索/中文分词的使用

文章目录文档用途详细信息文档用途 本文用于HGDB全文检索/中文分词的介绍&#xff0c;其介绍内容在附件&#xff0c;使用案例见正文 详细信息 一、创建扩展 highgo# create extension zhparser;CREATE EXTENSION highgo# \dFp List of text search parsers Schema…

baijian xiaomaodawang

我将为你创建一个基于Go 1.20.8和Gin框架的博客系统项目。以下是完整的实现方案&#xff1a; 项目创建流程 打开Goland&#xff0c;创建新项目选择Go项目&#xff0c;设置GOROOT为Go 1.20.8项目名称&#xff1a;blog-system启用Go Modules 项目结构 blog-system/ ├── cmd/ │…

Node.js的特性

Node.js的特性 Node.js具有几个显著特性&#xff1a; 事件驱动&#xff1a;Node.js采用事件驱动机制来处理请求和响应&#xff0c;这种机制可以帮助开发者处理大量并发请求&#xff0c;提高系统的性能和可靠性。 非阻塞I/O&#xff1a;Node.js使用异步I/O原语来实现非阻塞I/O操…

交叉编译linux-arm32位程序

目标平台rv1126 芯片 arm32位架构 在ubuntu22.04上交叉编译&#xff1a; 编译器下载地址&#xff1a; Linaro Releases 或者&#xff1a; wget http://releases.linaro.org/components/toolchain/binaries/6.4-2017.11/arm-linux-gnueabihf/gcc-linaro-6.4.1-2017.11-x86_6…

S 3.1深度学习--卷积神经网络

卷积层 图像原理 卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09; 图像在计算机中是一堆按顺序排列的数字&#xff0c;数值为 0 到 255。0 表示最暗&#xff0c;255 表示最亮。 图像识别 上图是只有黑白颜色的灰度图&#xff0c;而更普遍的图片表达…

【7】SQL 语句基础应用

SQL 语句基础应用where (筛选)where 子句可使用到运算符查询表中所有的数据查询表中的数据&#xff0c;必须满足 11&#xff08;相当于恒成立&#xff09;查询表中的 分数(score) 大于 80 分的学生查询表中 名称(name) 是 赵六 的数据查询表中 名称(name) 不等于 哈哈 的数据.查…

android 嵌套webview 全屏展示 页面延伸到状态栏且不被底部导航栏遮挡

我的项目是使用webview嵌套了一个uniapp打包出的h5 本来展示很正常&#xff0c;某天突然发现uniapp的底部导航被手机底部的导航栏挡住了&#xff0c;离奇&#xff0c;某天突然出现的 有些手机会设置展示底部导航按钮&#xff0c;有些手机会关闭底部导航 以下代码对这两种情况通…

【大前端】React Native 调用 Android、iOS 原生能力封装

&#x1f4d6; React Native 调用 Android、iOS 原生能力封装 1. 原理 React Native 的 核心思想&#xff1a;JS 层&#xff08;React 代码&#xff09;不能直接调用 Android/iOS 的 API。RN 提供了 Native Module 机制&#xff1a; Android&#xff1a;Java/Kotlin → 继承 Re…

HOOK安卓木马重大升级,勒索功能扩展至107项

勒索覆盖屏成新特征网络安全研究人员发现名为HOOK的安卓银行木马新变种&#xff0c;该恶意软件新增勒索软件式覆盖屏功能用于显示勒索信息。Zimperium zLabs研究员Vishnu Pratapagiri表示&#xff1a;"最新变种的显著特征是能够部署全屏勒索覆盖界面&#xff0c;旨在胁迫受…

GeoScene Maps 完整入门指南:从安装到实战

什么是GeoScene MapsGeoScene Maps是一套功能强大的Web地图开发平台&#xff0c;它基于现代Web技术构建&#xff0c;为开发者提供了丰富的地图服务和开发工具。与其他地图API相比&#xff0c;GeoScene Maps具有以下特点&#xff1a;核心优势全面的地图服务&#xff1a;支持2D/3…

本地大模型部署:Ollama 部署与 Python 接口调用全攻略

本地大语言模型实践&#xff1a;Ollama 部署与 Python 接口调用全攻略 一、引言 过去我们使用大语言模型&#xff08;LLM&#xff09;&#xff0c;更多依赖于 OpenAI API、Claude API 等云端服务。它们虽然强大&#xff0c;但存在两大问题&#xff1a; 隐私与数据安全&#xff…

OpenFeign:让微服务间调用像本地方法一样简单

引言&#xff1a;微服务通信的演进之路什么是OpenFeign&#xff1f;核心特性概览快速开始&#xff1a;搭建OpenFeign环境环境准备与依赖配置启用OpenFeign功能基础用法&#xff1a;从简单示例开始定义第一个Feign客户端在服务中调用Feign客户端进阶配置&#xff1a;深度定制Ope…

openharmony之一多开发:产品形态配置讲解

OpenHarmony 的“一多开发”指的是 一次开发&#xff0c;多端部署&#xff08;简称“一多”&#xff09;&#xff0c;即使用 一套代码工程&#xff0c;一次开发上架&#xff0c;按需部署到不同终端设备上 &#x1f3af; 核心概念速览 产品形态定义 写在前面&#xff1a;1.不同的…