设计模式——结构型模式——代理模式(静态代理、动态代理:JDK、CGLIB)

目录

代理模式

代理模式简介

代理模式的分类

代理模式组成

代理模式的优缺点

静态代理

背景前置

编写代码

JDK动态代理

编写代码

使用Arthas分析JDK动态代理底层原理

CGLIB动态代理

编写代码

三种代理的对比

 代理模式使用场景

代理模式

代理模式简介

        代理模式属于结构型模式。指一个对象本身不做实际的操作,而是通过其他对象来获取自己想要的结果。       

        产生背景:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

        意义:目标对象只需要关注自己的实现细节,通过代理来实现功能的增强,可以扩展目标对象的功能。同时体现了非常重要的变成模式,不能随便修改目标对象的源码,如果需要修改目标对象的源码对已有功能进行增强,此时可以通过修改代理的方式实现功能的扩展。

        例子:如果某人需要租房,此时可以借助于房屋中介或租赁公司,此时房屋中介或租赁公司相当于代理,可以让租房人找到适合自己的房屋。

代理模式的分类

        静态代理:静态代理就是在程序运行之前,代理类字节码.class就已编译好,通常一个静态代理类也只代理一个目标类,代理类和目标类都实现相同的接口。

        动态代理:动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时在虚拟机中程序自动创建的。

代理模式组成

        代理(Proxy)模式可以分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。

  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式的优缺点

        优点:保护、扩展、降低耦合度。

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

  • 代理对象可以扩展目标对象的功能;

  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

        缺点:增加系统的复杂性。

静态代理

背景前置

        火车站卖票:如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:

编写代码

        如上面背景所示,我们需要定义①抽象主题类:买票接口类。②真实主题类:代理的目标类(火车站)。③代理类:代售点,以实现对真实主题类功能的增强。

/*** 声明抽象类、定义公共方法*/
public interface SallTicket {void sale();
}/**
* 火车站类,实现买票接口
*/
public class TrainStation implements SallTicket{@Overridepublic void sale() {System.out.println("火车站正在卖票...");}
}/**
* 代理类:与目标类实现相同的接口,以达到对目标类方法的增强
*/public class SallProxy implements SallTicket{private TrainStation trainStation;public SallProxy(TrainStation trainStation){this.trainStation = trainStation;}@Overridepublic void sale() {System.out.println("代售点收取少量服务费...");trainStation.sale();}
}/*** 编写客户端测试类*/
public class Client {public static void main(String[] args) {SallProxy sallProxy = new SallProxy(new TrainStation());sallProxy.sale();}
}

接口、目标类、代理类之间的关系图:

  • 接口类SallTicket:定义了卖票的抽象公共方法sale()。
  • 目标类TrainStation:实现了接口类SallTicket,并重写方法sale()。
  • 代理类SallProxy:实现接口类SallTicket,且在方法内部传递了接口类的实现类也即目标类TrainStation。在代理类内部实现了原有卖票方法sale()的功能的增强,并且调用目标类TrainStation中的sale()方法达到了卖票的功能。
  • 测试类Client:构建代理类,并实现功能的验证。

JDK动态代理

编写代码

        JDK动态代理和静态代理不同的地方在于代理类的编写不同,因此接口类、目标类仍然沿用静态代理中创建的,编写JDK动态代理程序结构如下:

        其中代理类(ProxyFactory)和测试类(Client)代码如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*** 获取代理对象的工厂类* 代理类也实现了对应的接口*/
public class ProxyFactory {// 声明目标对象private TrainStation station = new TrainStation();// 获取代理对象的方法public SallTicket getProxyObject(){// 返回代理对象、代理对象也实现了目标接口/*** ClassLoader loader, 类加载器,用于加载代理类,可以通过目标对象获取类加载器* Class<?>[] interfaces, 代理类实现的接口的字节码对象* InvocationHandler h, 代理对象的调用处理程序*/SallTicket proxyObject = (SallTicket) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(), // 目标对象和代理类实现了相同的接口,可以通过目标对象获取代理类实现的接口的字节码对象new InvocationHandler() {   // 匿名内部类/*** @param proxy 代理对象,就是proxyObject* @param method 对接口中的方法进行封装的method对象* @param args 调用方法的实际参数* @return  返回值就是方法的返回值* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 对目标对象进行方法的增强System.out.println("代售点收取一定的服务费用(JDK)...");// 执行目标对象的方法Object obj = method.invoke(station, args);return obj;}});return proxyObject;}
}/*** 客户端,测试JDK动态代理对方法的增强*/
public class Client {public static void main(String[] args) {// 创建代理对象工厂ProxyFactory proxyFactory = new ProxyFactory();// 使用proxyFactory对象的方法获取代理对象SallTicket proxyObject = proxyFactory.getProxyObject();// 调用目标对象的方法proxyObject.sale();}
}

       接口、目标类、代理类之间的关系图: 

        上图中的ProxyFactory类并不是真正的JDK动态代理类,其本质上是一个代理工厂,通过Proxy类的静态方法newProxyInstance(...),传入目标类的字节码,代理类实现的接口,invocationHandler接口的实现内部类等参数在程序运行的时候动态的创建代理类,并且借助反射获取代理对象的方法,并且实现对目标方法的增强。

使用Arthas分析JDK动态代理底层原理

        在测试类Client中添加以下代码用于获取代理类的Class对象。

        随后借助于阿里开源工具Arthas,使用反编译命令jad com.sun.proxy.$Proxy0获取已加载到 JVM 中的类的源代码如下:

package com.sun.proxy;import com.itheima.proxy.dynamic.jdk.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements SellTickets {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void sell() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}

从上面的类中,我们可以看到以下几个信息:

  • 代理类($Proxy0)实现了SellTickets。

  • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

        可以分析代理类执行流程如下:

  1. 在测试类中通过代理对象调用sell()方法。
  2. 根据多态的特性,执行的是代理类($Proxy0)中的sall()方法。
  3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法。
  4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sall()方法。

CGLIB动态代理

编写代码

        CGLIB动态代理和静态代理不同的地方在于代理类的编写不同,并且不需要接口类,因此目标类仍然沿用静态代理中创建的,编写CGLIB动态代理程序结构如下:

        CGLIB是第三方提供的包,编写代码之前需要先引入jar包。

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

/*** 代理工厂*/
public class ProxyFactory implements MethodInterceptor {private TrainStation station = new TrainStation();// 获取代理对象public TrainStation getProxyObject(){// 创建Enhancer对象,类似于JDK动态代理的Proxy类Enhancer enhancer = new Enhancer();// 设置父类的字节码对象enhancer.setSuperclass(station.getClass());// 设置回调函数,传递的对象,是MethodInterceptor的子实现类对象enhancer.setCallback(this);// 创建代理对象TrainStation proxyObject = (TrainStation) enhancer.create();return proxyObject;}/*** @param o      代理对象* @param method 真实对象中的方法的Method实例* @param objects  实际参数* @param methodProxy 代理对象中的方法的method实例* @return 代理对象方法的返回值* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");TrainStation result = (TrainStation) method.invoke(station, objects);return result;}
}/*** 客户端*/
public class Client {public static void main(String[] args) {// 创建代理对象工厂ProxyFactory proxyFactory = new ProxyFactory();// 获取代理对象TrainStation proxyObject = proxyFactory.getProxyObject();proxyObject.sale();}
}

         接口、目标类、代理类之间的关系图:

        简单原理:通过自定义实现拦截器接口(MethodInterceptor)的类【也就是目标类】,并重写intercept()用于拦截增强被代理类的方法【类似于JDK动态代理中的invoke()方法】。通过Enhancer 类的 create()创建简单的代理类。

        CGLIB采用非常底层的字节码技术,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术的拦截所有父类的方法调用,顺势织入横切逻辑。(CGLIB在字节码的基础上,利用ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)

三种代理的对比

  • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。

  • JDK动态代理只能代理实现了接口的类,而CGLIB可以代理未实现任何接口的类。
  • JDK动态代理是实现了被代理对象所实现的接口CGLIB是继承了被代理对象。
  • JDK和CGLIB都是在运行期生成字节码,JDK是直接写Class字节码,CGLIB是使用ASM框架写Class字节码。
  • CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
  • 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

 代理模式使用场景

        此部分内容暂时不是很熟悉,等以后学习到再慢慢补充。

  • 远程(Remote)代理

    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

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

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

相关文章

Mybatis操作数据库的两种方式:Mapper代理模式

1.Mapper代理模式的特点 程序员没有写接口的子实现——直接获取数据库的数据 因为Mybatis定义了一套规则&#xff0c;对方法进行了实现&#xff0c;程序员只要遵循这套方法就可以直接使用 2.如何实现Mapper代理模式 步骤&#xff1a; 1.创建一个dao接口&#xff0c;在接口…

java项目之英语知识应用网站源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的英语知识应用网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 英语知识应用网站的主要…

【免费】AME最新Adobe Media Encoder电脑软件安装包2024-2018支持WIN和MAC

Adobe MediaEncoder「Me」2024是一款功能强大的转码和媒体处理软件&#xff0c;它不仅能轻松应对各种媒体文件的编码和导出需求&#xff0c;还支持多种视频格式和分辨率&#xff0c;让你的视频处理变得更加高效。此外&#xff0c;该软件界面简洁明了&#xff0c;操作简便&#…

【一步一步了解Java系列】:了解Java与C语言的运算符的“大同小异”

看到这句话的时候证明&#xff1a;此刻你我都在努力~ 加油陌生人~ 个人主页&#xff1a; Gu Gu Study ​​ 专栏&#xff1a;一步一步了解Java 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努…

【Element-UI快速入门】

文章目录 **Element-UI快速入门****一、Element-UI简介****二、安装Element-UI****三、引入Element-UI****四、使用Element-UI组件****五、自定义Element-UI组件样式****六、Element-UI布局组件****七、Element-UI表单组件****八、插槽&#xff08;Slots&#xff09;和主题定制…

【数据结构】排序(一)—— 希尔排序(思路演进版)

目录 一、常见的排序算法分类 二、常见排序算法的实现 2.1插入排序 2.1.1基本思想 2.1.2直接插入排序 思路 step1.单趟控制 step2.总体控制 代码实现 测试 特性总结 2.1.3 希尔排序( 缩小增量排序 ) 基本思想 思路演进 &#x1f308;1.代码实现单组排序&#…

端午节线上活动方案怎么写?

一年一端午&#xff0c;一岁一安康。 如果您想组织端午活动&#xff0c;却不知道如何安排&#xff0c;可以看看何策网&#xff0c;有很多案例参考&#xff0c;仿造模板修改即可。 下面分享一个线上端午节活动策划方案&#xff0c;希望能帮到你&#xff01; 端午节作为祭祖祈…

mysql集群NDBcluster引擎在写入数据时报错 (1114, “The table ‘ads‘ is full“)

问题描述&#xff1a;mysql集群在写入数据时&#xff0c;出现上述报错 问题原因&#xff1a;表数据已满&#xff0c;一般是在集群的管理节点设置里面datamemory的值太小&#xff0c;当数据量超过该值时就会出现该问题 解决方案&#xff1a; 修改集群管理节点的config.ini里面…

ICode国际青少年编程竞赛- Python-4级训练场-嵌套for循环练习2

ICode国际青少年编程竞赛- Python-4级训练场-嵌套for循环练习2 1、 for i in range(3):Dev.turnRight()for j in range(3):Dev.step(-3)Dev.turnRight()Dev.step(4-2*i)2、 for i in range(6):for j in range(2):Dev.step(2 2 * i)if i > 3: Dev.step(i - 2)Dev.turnRi…

C++小程序:同一路由器下两台计算机间简单通信(2/2)——客户端

客户端的程序结构前半部分与服务器端基本相同&#xff0c;后半部分也相对简单。相关函数的解释可以参考前文服务器端的内容。有关客户端的内容除个别地方外&#xff0c;就不再做长篇大论的解释。强调一点&#xff0c;如果将此程序移到其它电脑上运行&#xff0c;编译需要releas…

Ciphey无法安装的解决办法

安装过程纯属自己实践&#xff0c;满满干货 困扰我几天的问题终于解决了 我看着教程在window上安装 python3.8/python3.9/python3.10无论如何都安装不上&#xff0c; 在win10虚拟机仍然安装不上 可能是我电脑环境问题 解决办法&#xff1a; 在kali中安装&#xff0c;但是…

第13节 第二种shellcode编写实战(2)

在第二种shellcode编写实战(1)的基础上&#xff0c;新增加一个CAPI类&#xff0c;将所有用到的函数都在这个类中做动态调用的处理&#xff0c;这样使得整个shellcode功能结构更加清晰。 1. 新建类CAPI&#xff08;即api.h和api.cpp两个文件&#xff09;&#xff1a; api.h&…

flutter自定义日期选择器按日、按月、自定义开始、结束时间

导入包flutter_datetime_picker: 1.5.0 封装 import package:atui/jade/utils/JadeColors.dart; import package:flutter/cupertino.dart; import package:flutter/material.dart; import package:flutter_datetime_picker/flutter_datetime_picker.dart; import package:flut…

景源畅信电商:经营抖店需要电脑吗?

经营抖店是否需要电脑?这个问题看似简单&#xff0c;实则关乎着商家的运营效率和成本投入。在当前数字化、网络化的商业环境中&#xff0c;电脑已经成为了不可或缺的工具。那么&#xff0c;经营抖店究竟是否需要电脑呢?答案是肯定的。 一、高效处理订单 电脑能够高效地处理大…

如何在湖师大官网找到考研真题

今天学弟问我怎么找真题&#xff0c;我必须告诉他怎么找湖师大的真题&#xff0c;身为考研学子&#xff0c;这是必须要知道滴&#xff0c;尤其是自命题&#xff0c;是吧&#xff0c;话不多说&#xff0c;言归正传&#xff0c;我们开始吧&#xff01; 1 打开湖师大官网 什么&a…

树莓派nmap扫描

debian系统安装nmap&#xff1a; sudo apt install nmap安装nmap完成后&#xff0c;输入 ip route 来查看当前Wi-Fi路由器的ip地址。 第一行的default via后显示的便是网关地址&#xff0c;也就是路由器地址。 获取到路由器ip地址后&#xff0c;在终端中输入&#xff1a; …

一站式HMI软件开发套件eStation,让开发更简单高效

4月份举办的北京国际车展上全球首发车117辆&#xff0c;新能源车型278个&#xff0c;越来越多的车厂通过差异化和改善UI/UE体验&#xff0c;来获取更多用户的青睐。为快速响应差异化竞争需求&#xff0c;智能座舱HMI市场遇到以下挑战&#xff1a; 如何兼容不同项目开发人员编程…

力扣例题(用栈实现队列)

目录 链接. - 力扣&#xff08;LeetCode&#xff09; 描述 思路 push pop peek empty 代码 链接. - 力扣&#xff08;LeetCode&#xff09; 描述 思路 push 例如我们将10个元素放入栈中&#xff0c;假设最左边为栈顶&#xff0c;最右侧为栈底 则为10,9,8,7,6,5,4,3,…

嵌入式 - GPIO编程简介

An Introduction to GPIO Programming By Jeff Tranter Wednesday, June 12, 2019 编者按&#xff1a;本 2019 年博客系列是 ICS 最受欢迎的系列之一&#xff0c;现已更新&#xff08;2022 年 12 月&#xff09;&#xff0c;以确保内容仍然准确、相关和有用。 本博客是 Integr…

图片转word如何转换?

要将图片转换为Word文档&#xff0c;你可以使用以下方法之一&#xff1a; 以上这些方法都可以帮助你将图片中的文本转换为可编辑的Word文档&#xff0c;你可以根据自己的喜好和需求选择其中一种方法来操作。 使用OCR软件或在线工具&#xff1a;有许多OCR&#xff08;Optical Ch…