代理模式是什么

目录

代理模式

代理模式的组成

代理模式的作用

静态代理

静态代理实现步骤:

静态代理的缺点

动态代理

动态代理的实现

JDK 动态代理(接口代理)

jdk动态代理核心

JDK 动态代理类实现步骤:

CGLIB动态代理

CGLIB动态代理的核心

CGLIB 动态代理类实现步骤

JDK 动态代理和 CGLIB 动态代理对⽐


代理模式

定义:为其他对象提供⼀种代理以控制对这个对象的访问。在某些情况下,⼀个对 象不适合或者不能直接引⽤另⼀个对象,⽽代理对象可以在客户端和⽬标对象之间 起到中介的作⽤。

代理模式分为静态代理动态代理

代理模式的组成

代理模式一般由三个角色组成:

  1. Subject(抽象角色):声明了真实主题和代理主题的共同接口。

  2. Proxy(代理角色):具体的代理类,其内部持有对RealSubject的引用,因此具备完全的对RealSubject的代理权。客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些代码处理。

  3. RealSubject(真实角色):实现了真实的业务操作。

增强就是扩展对象功能,这里就是在代理的基础上可以添加自己的代码。

代理模式的作用

隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。

静态代理

静态代理中,我们对⽬标对象的每个⽅法的增强都是⼿动完成的,⾮常不灵活(⽐ 如接⼝⼀旦新增加⽅法,⽬标对象和代理对象都要进⾏修改)且麻烦(需要对每个 ⽬标类都单独写⼀个代理类)。在jvm上来说,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

静态代理实现步骤:

  1. 定义⼀个接⼝及其实现类;
  2. 创建⼀个代理类同样实现这个接⼝
  3. 将⽬标对象注⼊进代理类,然后在代理类的对应⽅法调⽤⽬标类中的对应方法。

这样的话,我们就可以通过代理类屏蔽对⽬标对象的访问,并且可以在⽬标⽅法执 ⾏前后做⼀些⾃⼰想做的事情。

定义接口(抽象接口)

public interface PayService {void pay();
}

实现接⼝(实现类)

public class AliPayService implements PayService {@Overridepublic void pay() {System.out.println("ali pay...");}
}

创建代理类并同样实现支付接口(代理类)

public class StaticProxy implements PayService{private final PayService payService;public StaticProxy(PayService payService) {this.payService = payService;}@Overridepublic void pay() {System.out.println("before...");payService.pay();System.out.println("after...");}
}

使用

public class Main {public static void main(String[] args) {PayService service = new AliPayService();PayService proxy = new StaticProxy(service);proxy.pay();}
}

静态代理的缺点

上边的代码我们可以看出代理类和实现类都实现了抽象接口,这也是静态代理的一个前提,就是实现类和代理类要是实现同一个接口。

而且静态代理提前写好了,会占用内存,而且代理每个对象的每个增强都是自己手写的,代码量也越来越大,我们想要减少代码量并且想要在我们调用的时候,代理再去创建。因此,有了动态代理。

动态代理

对比于静态代理来说,动态代理更加灵活。我们不需要针对每个⽬标类都单独创建 ⼀个代理类,并且也不需要我们必须实现接⼝,我们可以直接代理实现类( CGLIB 动态代理机制)。 从 JVM ⻆度来说,动态代理是在运⾏时动态⽣成类字节码,并加载到 JVM 中 的。

动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

动态代理的实现

JDK 动态代理

CGLIB 动态代理等。

JDK 动态代理(接口代理)

jdk动态代理核心

在 Java 动态代理机制中 InvocationHandler 接⼝Proxy 类是核⼼。

  • Invocationhandler,这是一个很简单的接口,里面就定义了一个方法:invoke(). 这个invoke方法是最终代理类代理方法的实现 .
public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

 invoke()方法有下面三个参数:

  1. proxy :动态生成的代理类
  2. method : 与代理类对象调用的方法相对应
  3. args : 当前 method 方法的参数

  • Proxy,他的作用主要用来创建一个代理类。最常用的方法就是newProxyInstance()
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

这个方法一共有 3 个参数:

  1. loader :类加载器,用于加载代理对象。
  2. interfaces : 被代理类实现的一些接口;
  3. h : 实现了 InvocationHandler 接口的对象;

JDK 动态代理类实现步骤:

  1.  定义⼀个接⼝及其实现类;
  2.  ⾃定义 InvocationHandler 并重写invoke⽅法,在 invoke ⽅法中我们会调⽤ 原⽣⽅法(被代理类的⽅法)并⾃定义⼀些处理逻辑;
  3.  通过 Proxy.newProxyInstance() ⽅法创建代理对象;

定义代理类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {//⽬标对象即就是被代理对象private Object target;public JDKInvocationHandler(Object target) {this.target = target;}//proxy代理对象@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throw
s Throwable {//1.安全检查System.out.println("安全检查");//2.记录⽇志System.out.println("记录⽇志");//3.时间统计开始System.out.println("记录开始时间");//通过反射调⽤被代理类的⽅法Object retVal = method.invoke(target, args);//4.时间统计结束System.out.println("记录结束时间");return retVal;}
}

创建一个代理对象并使用

public static void main(String[] args) {PayService target= new AliPayService();//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建PayService proxy = (PayService) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{PayService.class},new JDKInvocationHandler(target));proxy.pay();}

CGLIB动态代理

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类。 为了解决这个问题,我们可以⽤ CGLIB 动态代理机制来避免。cglib 是针对类来实现代理的,他的原理是对指定的目标类生成一个子类并通过回调的方式来实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。

CGLIB(Code Generation Library)是⼀个基于ASM的字节码⽣成库,它允许我们 在运⾏时对字节码进⾏修改和动态⽣成。CGLIB 通过继承⽅式实现代理。很多知名的开源框架都使⽤到了CGLIB, 例如 Spring 中的 AOP 模块中:如果⽬标对象 实现了接⼝,则默认采⽤ JDK 动态代理,否则采⽤ CGLIB 动态代理。

CGLIB动态代理的核心

在 CGLIB 动态代理机制中 MethodInterceptor 接⼝Enhancer 类是核⼼。

你需要⾃定义 MethodInterceptor 并重写 intercept ⽅法,intercept ⽤于拦截增 强被代理类的⽅法。

public interface MethodInterceptor
extends Callback{// 拦截被代理类中的⽅法public Object intercept(Object obj, java.lang.reflect.Method method, Ob
ject[] args,MethodProxy proxy) throws Throwable;
}

intercept()方法有4个参数:

  1.  obj : 被代理的对象(需要增强的对象)
  2.  method : 被拦截的⽅法(需要增强的⽅法)
  3.  args : ⽅法⼊参
  4.  proxy : ⽤于调⽤原始⽅法

CGLIB 动态代理类实现步骤

  1.  定义⼀个类;
  2.  ⾃定义 MethodInterceptor 并重写 intercept ⽅法,intercept ⽤于拦截增强 被代理类的⽅法,和 JDK 动态代理中的 invoke ⽅法类似;
  3.  通过 Enhancer 类的 create()创建代理类

添加依赖

和JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于⼀个开源项 ⽬,如果你要使⽤它的话,需要⼿动添加相关依赖。

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

⾃定义 MethodInterceptor(⽅法拦截器)

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {//被代理对象private Object target;public CGLIBInterceptor(Object target){this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] args, Method
Proxy methodProxy) throws Throwable {//1.安全检查System.out.println("安全检查");//2.记录⽇志System.out.println("记录⽇志");//3.时间统计开始System.out.println("记录开始时间");//通过cglib的代理⽅法调⽤Object retVal = methodProxy.invoke(target, args);//4.时间统计结束System.out.println("记录结束时间");return retVal;}
}

创建代理类并使用

import org.springframework.cglib.proxy.Enhancer;
public static void main(String[] args) {PayService target= new AliPayService();PayService proxy= (PayService) Enhancer.create(target.getClass(),new CGLIBInterceptor(target));proxy.pay();}

JDK 动态代理和 CGLIB 动态代理对⽐

1. JDK 动态代理只能代理实现了接⼝的类或者直接代理接⼝,⽽ CGLIB 可以代 理未实现任何接⼝的类。

2. JDK 动态代理实现InvocationHandler,重写invoke方法,通过生成被代理类的子类来增强代码。CGLIB 动态代理是通过实现MethodInterceptor,重写intercept方法,⽣成⼀个被代理类的⼦类来拦截被代理类的⽅法调用,因此不能代理声明为 final。

性能: ⼤部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更 加明显。

补充一点

Spring代理选择

1. proxyTargetClass 为false, ⽬标实现了接⼝, ⽤jdk代理

2. proxyTargetClass 为false, ⽬标未实现接⼝, ⽤cglib代理

3. proxyTargetClass 为true, ⽤cglib代理

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

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

相关文章

远程连接身份验证错误,又找不到加密Oracle修正

一、问题描述 远程连接服务器出现了错误&#xff0c;错误信息为&#xff1a;远程连接身份验证错误&#xff0c;又找不到加密Oracle修正。 二、原因分析 出错原因&#xff1a;Windows的CVE-2018-0886 的 CredSSP 更新将CredSSP 身份验证协议默认设置成了“缓解”&#xff0c;…

Informer 论文学习笔记

论文&#xff1a;《Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting》 代码&#xff1a;https://github.com/zhouhaoyi/Informer2020 地址&#xff1a;https://arxiv.org/abs/2012.07436v3 特点&#xff1a; 实现时间与空间复杂度为 O ( …

轻松批量文件改名!一键翻译重命名文件夹/文件,省时高效!」

繁忙的数字时代&#xff0c;我们经常需要处理大量的文件和文件夹。而手动逐个更改文件名不仅费时费力&#xff0c;还容易出错。因此&#xff0c;我们为您带来了一款强大的工具——批量文件改名软件&#xff01;现在&#xff0c;您可以一键翻译重命名文件夹和文件&#xff0c;轻…

在centos7.9安装tomcat8,并配置服务启动脚本,部署jpress应用

目录 一、简述静态网页和动态网页的区别 二、简述 Webl.0 和 Web2.0 的区别 三、 安装Tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用 3.1、Tomcat简介 3.2、安装Tomcat 3.2.1、配置环境 3.2.2、安装JDK 3.2.3、安装tomcat8 3.2.4、访问主页&#xff1…

go 如何知道一个对象是分配在栈上还是堆上?

如何判断变量是分配在栈&#xff08;stack&#xff09;上还是堆&#xff08;heap&#xff09;上&#xff1f; Go和C不同&#xff0c;Go局部变量会进行逃逸分析。如果变量离开作用域后没有被引用&#xff0c;则优先分配到栈上&#xff0c;否则分配到堆上。判断语句&#xff1a;…

数据可视化(4)散点图及面积图

1.简单散点图 #散点图 #scatter(x,y) x数据&#xff0c;y数据 x[i for i in range(10)] y[random.randint(1,10) for i in range(10)] plt.scatter(x,y) plt.show()2.散点图分析 #分析广告支出与销售收入相关性 dfcarpd.read_excel(广告支出.xlsx) dfdatapd.read_excel(销售…

1.3 eureka+ribbon,完成服务注册与调用,负载均衡源码追踪

本篇继先前发布的1.2 eureka注册中心&#xff0c;完成服务注册的内容。 目录 环境搭建 采用eurekaribbon的方式&#xff0c;对多个user服务发送请求&#xff0c;并实现负载均衡 负载均衡原理 负载均衡源码追踪 负载均衡策略 如何选择负载均衡策略&#xff1f; 饥饿加载…

怎么在ros中怎么在一个节点总把一个矩阵保存为一个参数到参数管理器中,然后在另一个节点中读取这个参数

在ROS中,可以通过Parameter Server在节点之间共享参数。要在一个节点中保存矩阵作为参数,可以使用set_param()函数: python import rospy import numpy as npmatrix np.array([[1, 2], [3, 4]]) rospy.set_param("/matrix_param", matrix.tolist())这里我们把NumPy矩…

抖音seo短视频账号矩阵系统技术开发简述

说明&#xff1a;本开发文档适用于抖音seo源码开发&#xff0c;抖音矩阵系统开发&#xff0c;短视频seo源码开发&#xff0c;短视频矩阵系统源码开发 一、 抖音seo短视频矩阵系统开发包括 抖音seo短视频账号矩阵系统的技术开发主要包括以下几个方面&#xff1a; 1.前端界面设…

unity连接MySQL数据库并完成增删改查

数据存储量比较大时&#xff0c;我就需要将数据存储在数据库中方便使用&#xff0c;尤其是制作管理系统时&#xff0c;它的用处就更大了。 在编写程序前&#xff0c;需要在Assets文件夹中创建plugins文件&#xff0c;将.dll文件导入&#xff0c;文件从百度网盘自取&#xff1a;…

使用文心一言等智能工具指数级提升嵌入式/物联网(M5Atom/ESP32)和机器人操作系统(ROS1/ROS2)学习研究和开发效率

以M5AtomS3为例&#xff0c;博客撰写效率提升10倍以上&#xff1a; 0. Linux环境Arduino IDE中配置ATOM S3_zhangrelay的博客-CSDN博客 1. M5ATOMS3基础01按键_zhangrelay的博客-CSDN博客 2. M5ATOMS3基础02传感器MPU6886_zhangrelay的博客-CSDN博客 3. M5ATOMS3基础03给RO…

【MySQL】表的增删查改

文章目录 一、创建表create二、查看表desc三、修改表3.1 修改表名alter3.2 在表中插入数据insert3.3 在表中新增字段alter3.4 修改指定列的属性alter3.5 移除表中的一列alter3.6 修改表中某一列的列名alter 四、删除表drop 一、创建表create mysql> create table if not ex…

Neo4j文档阅读笔记-Installation and Launch Guide

安装&#xff08;Windows&#xff09; ①找到下载好的Neo4j Desktop文件&#xff0c;然后双击进行安装&#xff1b; ②安装Neo4j Desktop根据下一步进行安装。 启动 ①激活 打开Neo4j Desktop应用程序后&#xff0c;将激活码输入到“Activation Key”窗口中。 ②创建数据库…

AMEYA:尼得科科宝滑动型DIP开关CVS产品参数及价格​

日本电产尼得科科宝滑动型DIP开关CVS采用紧凑设计&#xff0c;3bit产品&#xff0c;旋钮把手高度为0.2mm&#xff0c;操作性良好端子为1mm间距&#xff0c;电路数丰富(2,3,4,8)端接样式为鸥翼式&#xff0c;J形引线使用树脂材料符合UL认证94V-0 符合RoHS规范。 日本电产尼得科科…

Vol的学习

首先学习基础用法 1.查看系统基本信息 vol.py -f 路径 imageinfo 2.查看进程命令行 vol.py -f 路径 --profile系统版本 cmdline vol.py -f 路径 --profile版本 cmdscan 3.查看进程信息 vol.py -f 路径 --profile系统 pslist 通过树的方式返回 vol.py -f 路径 --profile系统…

FSDirectory 与 RAMDirectory

FSDirectory和RAMDirectory是Lucene搜索引擎中两种不同的Directory实现&#xff0c;用于管理索引数据的存储。Lucene是一个强大的开源搜索引擎库&#xff0c;它用于创建全文搜索功能&#xff0c;而Directory则是用来表示索引数据的存储位置。 FSDirectory: FSDirectory是将索引…

postgis mvt矢量切片 django drf mapboxgl

postgis mvt矢量切片 django drf mapboxgl 0.前提 [1] 静态的矢量切片可以采用 tippecanoe 生成&#xff0c;nginx代理&#xff0c;这种数据是不更新的&#xff1b; [2] 动态的矢量切片&#xff0c;一般采用postgis生成。基本上矢量切片80%的厂商都采用postgis&#xff0c;确实…

【Docker】部署 mysql8.0 无法访问

文章目录 &#x1f5fd;先来说我的是什么情况&#x1fa81;问题描述&#x1fa81;解决方法&#xff1a;✔️1 重启iptables✔️2 重启docker &#x1fa81;其他有可能连不上的原因✔️1 客户端不支持caching_sha2_password的加密方式✔️2 my.conf 配置只有本机可以访问 &#…

05 Ubuntu下安装.deb安装包方式安装vscode,snap安装Jetbrains产品等常用软件

使用deb包安装类型 deb包指的其实就是debian系统&#xff0c;ubuntu系统是基于debian系统的发行版。 一般我们会到需要的软件官网下载deb安装包&#xff0c;然后你既可以采用使用“软件安装”打开的方法来进行安装&#xff0c;也可以使用命令行进行安装。我推荐后者&#xff…

3分钟创建超实用的中小学新生录取查询系统,现在可以实现了

在新学期开始之际&#xff0c;作为招生负责人&#xff0c;您是否已经做好准备来迎接新学年的招生工作呢&#xff1f;录取新生所需的任务包括录入成绩信息、核对招生要求以及公布新生录取信息等&#xff0c;这些工作繁重而具有挑战性&#xff0c;给负责招生的老师带来了巨大的压…