【Java】代理模式

代理模式

代理模式是指给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问

代理模式是一种结构型设计模式

背景

如果不采用代理,对一个类的多个方法进行监控时,重复的代码总是重复出现,不但破坏了原方法,如果要实现多个监控,将会对代码造成大量冗余。同时,还导致业务代码,与非业务的监控代码掺杂在一起,不利于扩展和维护。代理类在无限制膨胀,就需要无限的修改业务代码。

而采用代理后,原方法不需要做任何改动,操作的是原方法的代理对象,而原方法不用做任何修改,就实现了被监控。

结构

代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象(RealSubject)和代理对象(Proxy),引入了抽象层(Subject)

在这里插入图片描述

代理模式角色:

  • Subject(抽象主题角色):定义 代理类和真实主题的 公共对外方法,也是代理类代理真实主题的方法
  • RealSubject(真实主题角色):真正实现业务逻辑的类
  • Proxy(代理主题角色):用来代理和封装真实主题

分类

按使用场景

代理模式按照职责(使用场景)来分类,至少可以分为以下几类:

  • 远程代理
  • 虚拟代理
  • Copy-on-Write 代理
  • 保护(Protect or Access)代理
  • Cache代理
  • 防火墙(Firewall)代理
  • 同步化(Synchronization)代理
  • 智能引用(Smart Reference)代理

按字节码的创建时机

如果根据字节码的创建时机来分类,可以分为 静态代理和动态代理

静态代理

所谓静态是指在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就已经确定

优点:

  • 实现简单
  • 没有侵入原代码
  • 可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性

代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,即对外暴露的是代理对象而真正调用的是 Real Object

缺点:

  • 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
    • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
    • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
  • 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护

实例:

通过静态代理对 UserServiceImpl 进行功能增强,在调用selectupdate之前记录一些日志。

1、编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl

public interface UserService {public void select();   public void update();
}public class UserServiceImpl implements UserService {  public void select() {  System.out.println("查询 selectById");}public void update() {System.out.println("更新 update");}
}

2、写一个代理类 UserServiceProxy,代理类需要实现 UserService

public class UserServiceProxy implements UserService {private UserService target; // 被代理的对象public UserServiceProxy(UserService target) {this.target = target;}public void select() {before();target.select();    // 这里才实际调用真实主题角色的方法after();}public void update() {before();target.update();    // 这里才实际调用真实主题角色的方法after();}private void before() {     // 在执行方法之前执行System.out.println(String.format("log start time [%s] ", new Date()));}private void after() {      // 在执行方法之后执行System.out.println(String.format("log end time [%s] ", new Date()));}
}

3、测试

public class Client {public static void main(String[] args) {UserService userServiceImpl = new UserServiceImpl();UserService proxy = new UserServiceProxy(userServiceImpl);proxy.select();proxy.update();}
}
动态代理

动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,在运行前并不存在代理类的字节码文件

让代理类动态的生成(动态代理),可以避免静态代理复杂环境下复杂度高及不易维护的缺点

动态代理的优势就是实现无侵入式的代码扩展,实现AOP编程,这是静态代理无法实现的

实现动态代理的两种常见方式:

  • JDK动态代理:通过实现接口的方式
  • CGLIB动态代理:通过继承类的方式

Java 动态代理使用 Java 原生的反射 API 进行操作(运行期),在生成类上比较高效;而CGLIB 相比于 JDK 动态代理更加强大,CGLIB使用ASM框架直接对字节码进行操作(编译期),在类的执行过程中比较高效。

CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,JDK方式要更合适

JDK动态代理

Java标准库提供的一种动态代理(Dynamic Proxy)的机制,允许在运行期动态创建某个interface的实例。

jdk动态代理之所以只能代理接口是因为代理类本身已经继承了Proxy,而java是不允许多重继承的,但是允许实现多个接口。

由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK动态代理方式无法解决;

实现方式:

只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象。

优点

  • 解决了静态代理中冗余的代理实现类问题。

缺点

  • JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

实例:

通过静态代理对 UserServiceImpl 进行功能增强,在调用selectupdate之前记录一些日志。

1、编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl

public interface UserService {public void select();   public void update();
}public class UserServiceImpl implements UserService {  public void select() {  System.out.println("查询 selectById");}public void update() {System.out.println("更新 update");}
}

2、写一个代理类 UserServiceProxy,代理类需要实现 UserService

public class UserServiceProxy {public static Object getObject(final UserService target){Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(String.format("log start time [%s] ", new Date()));method.invoke(target, args);System.out.println(String.format("log end time [%s] ", new Date()));return null;}});return proxyInstance;}
}

3、测试

public class Client {public static void main(String[] args) {UserService userServiceImpl = new UserServiceImpl();UserService proxy =(UserService) UserServiceProxy.getObject(userServiceImpl);proxy.select();proxy.update();}
}
CGLIB 代理

CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。

由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

实现方式:

实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。

优点

  • 没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。

缺点

  • 技术实现相对难理解

实例:

通过静态代理对 UserServiceImpl 进行功能增强,在调用selectupdate之前记录一些日志。

1、编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl

public interface UserService {public void select();   public void update();
}public class UserServiceImpl implements UserService {  public void select() {  System.out.println("查询 selectById");}public void update() {System.out.println("更新 update");}
}

2、写一个代理类 UserServiceProxy,代理类需要实现 UserService

public class UserServiceProxy {public static Object getObject(final UserService target){Object proxyObj = Enhancer.create(obj.getClass(), new MethodInterceptor() {public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println(String.format("log start time [%s] ", new Date()));method.invoke(target, objects);System.out.println(String.format("log end time [%s] ", new Date()));return null;}});return proxyObj;}
}

3、测试

public class Client {public static void main(String[] args) {UserService userServiceImpl = new UserServiceImpl();UserService proxy =(UserService) UserServiceProxy.getObject(userServiceImpl);proxy.select();proxy.update();}
}

参考

Java 动态代理详解 - 掘金 (juejin.cn)

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

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

相关文章

STM32 看门狗

目录 背景 独立看门狗(IWDG) 寄存器访问保护 窗口看门狗(WWDG) 程序 独立看门狗 设置独立看门狗程序 第一步、使能对独立看门狗寄存器的写操作 第二步、设置预分频和重装载值 第三步、喂狗 第四步、使能独立看门狗 喂狗…

LLM论文笔记 15: Transformers Can Achieve Length Generalization But Not Robustly

Arxiv日期:2024.2.14机构:Google DeepMind / University of Toronto 关键词 长度泛化位置编码数据格式 核心结论 1. 实验结论:十进制加法任务上的长度泛化最佳组合: FIRE位置编码 随机化位置编码 反向数据格式 索引提示&…

超详细!一文搞定PID!嵌入式STM32-PID位置环和速度环

本文目录 一、知识点1. PID是什么?2. 积分限幅--用于限制无限累加的积分项3. 输出值限幅--用于任何pid的输出4. PID工程 二、各类PID1. 位置式PID(用于位置环)(1)公式(2)代码使用代码 2. 增量式…

【Linux探索学习】第二十八弹——信号(下):信号在内核中的处理及信号捕捉详解

Linux学习笔记: https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言: 在前面我们已经学习了有关信号的一些基本的知识点,包括:信号的概念、信号产生和信号处理等,今天我们重…

Qt中使用QPdfWriter类结合QPainter类绘制并输出PDF文件

一.类的介绍 1.QPdfWriter介绍 Qt中提供了一个直接可以处理PDF的类,这就是QPdfWriter类。 (1)PDF文件生成 支持创建新的PDF文件或覆盖已有文件,通过构造函数直接绑定文件路径或QFile对象; 默认生成矢量图形PDF&#…

快速上手gdb/cgdb

Linux调试器-gdb使用 1.背景2.调试原理、技巧命令2.1指令2.2 本质2.3 技巧 1.背景 程序的发布方式有两种,debug模式和release模式 Linux gcc/g出来的二进制程序,默认是release模式 要使用gdb调试,必须在源代码生成二进制程序的时候, 加上 -g…

linux网络编程(1.5w字+内部程序理解网络)

目录 核心大图: 网络字节序 网络字节序与主机字节序 地址转换函数 一、inet_ntoa函数 二、inet_aton函数 三、inet_aton和inet_ntoa的测试 in_addr转字符串的函数: socket编程接口 socket 常见API 1.socket 参数1:int af 参数2:…

windows环境下用docker搭建php开发环境dnmp

安装WSL WSL即Linux子系统,比虚拟机占用资源少,安装的前提是系统必须是win10以上。 WSL的安装比较简单,网上有很多教程,例如:WSL简介与安装流程(Windows 下的 Linux 子系统)_wsl安装-CSDN博客&…

Nginx之rewrite重写功能

目录 一、rewrite概述 1、rewrite功能 2、跳转场景 二、标准配置指令 1、rewrite日志记录指令 2、未初始化变量告警日志记录指令 3、rewrite 指令 3.1 正则表达式 三、rewrite模块使用实例 1.基于域名的跳转 2.基于客户端 IP 访问跳转 3.?基于旧域名跳转到新域名后…

Mybatis(一)

配置文件 必要的用户密码要修改, 还有绿色线的名字要修改成数据库的 配置文件直接cv 创建 复习之前的知识进行分层处理 与前面一一对应, 后面三个发现后面输出是null, 没有一一对应, 后面再解释解决方法 运行发现, 输出正常 idea的测试类 两个注解了解 记得加上这个, 不然无…

一周学会Flask3 Python Web开发-http响应状态码

锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 在Flask程序中,客户端发出的请求触发相应的视图函数,获取返回值会作为响应的主体,最后生成…

七星棋牌源码高阶技术指南:6端互通、200+子游戏玩法深度剖析与企业级搭建实战(完全开源)

在棋牌游戏行业高速发展的今天,如何构建一个具备高并发、强稳定性与多功能支持的棋牌游戏系统成为众多开发者和运营团队关注的焦点。七星棋牌全开源修复版源码 凭借其 六端互通、200子游戏玩法、多省区本地化支持,以及 乐豆系统、防沉迷、比赛场、AI智能…

C++和OpenGL实现3D游戏编程【总览】

欢迎来到zhooyu的游戏专栏。 主页网址:【zhooyu】 专栏网址:【C和OpenGL实现3D游戏编程】 🌟🌟🌟这里将通过一个OpenGL实现3D游戏编程实例教程,带大家深入学习OpenGL知识。知识无穷而人力有穷,…

pycharm社区版有个window和arm64版本,到底下载哪一个?还有pycharm官网

首先pycharm官网是这一个。我是在2025年2月16日9:57进入的网站。如果网站还没有更新的话,那么就往下滑一下找到 community Edition,这个就是社区版了免费的。PyCharm:适用于数据科学和 Web 开发的 Python IDE 适用于数据科学和 Web 开发的 Python IDE&am…

GPT-Sovits:语音克隆训练-遇坑解决

前言 本来以为3050完全无法执行GPT-Sovits训练的,但经过实践发现其实是可以,并且仅花费了十数分钟便成功训练和推理验证了自己的语音模型。 官方笔记:GPT-SoVITS指南 语雀 项目地址:https://github.com/RVC-Boss/GPT-SoVITS 本人…

8 SpringBootWeb案例(上): 查询【分页功能(分页插件)】、删除、新增、修改

文章目录 前言:SpringBootWeb案例1. 准备工作1.1 需求&环境搭建1.1.1 需求说明1.1.2 环境搭建1.2 开发规范1.2.1 开发规范-REST(不强求非要这种风格,传统风格有时候更方便)1.2.2 开发规范-统一响应结果和异常处理1.2.3 开发流程2. 部门管理2.1 查询部门2.1.1 原型和需求…

深入了解 DevOps 基础架构:可追溯性的关键作用

在当今竞争激烈的软件环境中,快速交付强大的应用程序至关重要。尽管如此,在不影响质量的情况下保持速度可能是一项艰巨的任务,这就是 DevOps 中的可追溯性发挥作用的地方。通过提供软件开发生命周期 (SDLC) 的透明视图…

用C++ Qt实现安卓电池充电动效 | 打造工业级电量控件

一、为什么需要自定义电池控件? 在工业控制、车机系统、智能硬件等领域的UI开发中,电池状态显示是高频出现的UI组件。通过实现一个支持颜色渐变、动态充电动画、警戒阈值提示的电池控件,开发者可以系统掌握以下核心能力: Qt绘图…

一批起飞猪名单配图

好久没有使用风口猪选股指标了,今天去玩了一把,发现起飞猪指标显示了好多一批猪票 华曙高科 汉威科技 双林股份 曼恩斯特 长盈精密 江苏雷利 双飞集团 奥飞数据 硅宝科技 水晶光电 长盈精密

机器学习笔记——常用损失函数

大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本笔记介绍机器学习中常见的损失函数和代价函数,各函数的使用场景。 热门专栏 机器学习 机器学习笔记合集 深度学习 深度学习笔记合集 文章目录 热门…