12、设计模式之代理模式(Proxy)

news2024/11/25 16:33:28

一、什么是代理模式
代理模式属于结构型设计模式。为其他对象提供一种代理以控制对这个对象的访问。

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

二、分类

代理模式分为三类:

  1. 静态代理
  2. 动态代理
  3. CGLIB代理

三、特点
优点:

代理模式可以隐藏真是对象的实现细节,使客户端无需知晓真实对象的工作方式和结构。
通过代理类来间接访问真实类,可以在不修改真实类的情况下,对其进行扩展、优化或添加安全措施。
代理模式实现起来简单,易于扩展和维护,符合面向对象设计原则中的开闭原则。
缺点:

代理模式可能会引入额外的复杂性和间接性,增加程序设计和维护的难度。
对象代理可能会降低系统性能,特别是在处理大数据量或频繁调用的情况下,因为代理需要额外的计算和网络通信开销。
四、应用场景
4.1 生活场景
Window是的快捷键。
支票、银行卡。
律师。
4.2 Java场景

AOP:通过定义切面、切入点和通知等,Spring
AOP在运行时生成代理对象,将切面逻辑织入到目标对象的方法调用中。代理对象在方法调用前后执行附加操作,如日志记录、性能监控等。
动态代理(JDK动态代理、CGLIB代理):当Bean类实现了接口时,Spring使用JDK动态代理来为Bean生成代理对象;当Bean类没有实现接口时,Spring使用CGLIB代理来生成代理对象。

五、代码实现
5.0 代码结构
在这里插入图片描述
下面就以房东和租客为例,分别介绍一下静态代理、jdk动态代理和cglib代理。

5.1 静态代理
静态代理是一种在代码编写期进行代理类和被代理类的关联的代理方式。

具体实现是创建一个代理类,通常需要实现与被代理类相同的接口或继承被代理类。
在这里插入图片描述
房东接口类:Landlord1Service,

注意:静态代理实现它的真实对象只能有一个,多个的话,代理对象不能确定哪个对象需要被代理,会导致报错,JDK动态代理没这个问题

/**
 * 房东
 * 
 */
public interface Landlord1Service {
    /**
     * 出租
     * @param money 金额
     * @return
     */
    void rent(Integer money);
}
租客:TenantImpl

/**
 * 租客
 * @author Created by njy on 2023/5/30
 */
@Component
public class TenantImpl implements Landlord1Service {
 
    @Override
    public void rent(Integer money) {
        System.out.println("租下"+money+"元一个月的房子");
    }
}

静态代理:ProxyImpl

/**
 * 中介
 * 
 */
@Component
public class ProxyImpl implements Landlord1Service {
 
    /**
     * 房东有很多套房子,不想亲自出马了,于是找来了中介
     */
    @Autowired
    private Landlord1Service target;
 
    /**
     * 优点就是在不改变原来的实现类的情况下对方法实现了增强
     * 缺点是如果原来的接口新增了方法,那么这里也要对应实现新的方法
     * @param money 金额
     * @return
     */
    @Override
    public void rent(Integer money) {
        System.out.println("[静态代理]交中介费");
        target.rent(money);
        System.out.println("[静态代理]中介负责维修管理");
    }
}

测试:


@SpringBootTest
public class TestProxy {
    @Autowired
    private TenantImpl tenant;
 
    @Autowired
    private ProxyImpl proxy;
 
    //1.静态代理
    @Test
    void TestStatic(){
        tenant.rent(1000);
        System.out.println();
        proxy.rent(2000);
    }
}

适用场景:

对象必须实现一个或多个接口
代理类的代理方法不需要额外的逻辑

5.2 JDK动态代理
JDK动态代理是一种比较常见的代理方式,它是在程序运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成。
在这里插入图片描述

房东接口类:Landlord2Service

public interface Landlord2Service {
 
    /**
     * 出租
     * @param money
     * @return
     */
    void rent(Integer money);
}

租客1:Teant1Impl

@Component
public class Teant1Impl implements Landlord2Service{
    @Override
    public void rent(Integer money) {
        System.out.println("tenant1租下"+money+"元一个月的房子");
    }
}

租客2:Teant2Impl


@Component
public class Tenant2Impl implements Landlord2Service {
    @Override
    public void rent(Integer money) {
        System.out.println("tenant2租下"+money+"元一个月的房子");
    }
}

JDK动态代理:JDKProxy

/**
 * JDK动态代理:就是把代理抽象了一下
 * 
 */
public class JDKProxy {
 
    private Object target;
 
    public JDKProxy(Object target){
        this.target=target;
    }
 
    /**
     * 给目标对象生成代理对象
     * @return 代理生成的对象
     */
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                //这里是要实现jdk代理InvocationHandler的接口,lambda表达式
                (proxy,method,args)->{
                    //执行对象方法
                    System.out.println("[JDK动态代理]交中介费");
                    method.invoke(target,args);
                    System.out.println("[JDK动态代理]中介负责维修管理");
                    return null;
                });
    }
}

Test:


@SpringBootTest
public class TestProxy {
    @Autowired
    private Teant1Impl teant1;
    @Autowired
    private Tenant2Impl tenant2;
    //2.JDK动态代理
    @Test
    void TestJDK(){
        Landlord2Service proxyInstance1 = (Landlord2Service) new JDKProxy(teant1).getProxyInstance();
        proxyInstance1.rent(2500);
 
        System.out.println();
        Landlord2Service proxyInstance2 = (Landlord2Service) new JDKProxy(tenant2).getProxyInstance();
        proxyInstance2.rent(2500);
    }
}

适用场景:

对象必须实现一个或多个接口
代理类的代理方法不需要额外的逻辑

5.3 Cglib代理
CGLIB代理是在运行时动态生成代理类的方式,它使用的库是cglib,和JDK代理相比,它不是动态的生成一个实现了接口的代理类,而是直接在内存中构建一个被代理类的子类,并重写父类的方法来进行代理。
在这里插入图片描述
房东类:Landlord3Service

@Component
public class Landlord3Service {
    /**
     * 出租房屋
     * @param money
     * @return
     */
    public void rent(Integer money){
        System.out.println("租下"+money+"元一个月的房子");
    }
}

Cglib代理类:CglibProxy

/**
 * JDKProxy:cglib子类代理工厂
 * 1.代理的类不能为final
 * 2.目标对象的方法如果为final/static,那么就不会被拦截,也不会执行目标对象的业务方法
 * 
 */
public class CglibProxy implements MethodInterceptor {
 
    /**
     * 目标对象
     */
    private final Object target;
 
    public CglibProxy(Object target){
        this.target=target;
    }
 
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en=new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }
 
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("[Cglib代理]交中介费");
        method.invoke(target,objects);
        System.out.println("[Cglib代理]中介负责维修管理");
        return null;
    }
}

测试:


@SpringBootTest
@RequiredArgsConstructor
public class TestProxy {
    @Autowired
    private Landlord3Service landlord3Service;
    //3.Cglib代理
    @Test
    void TestCglib(){
        Landlord3Service proxyInstance = (Landlord3Service) new CglibProxy(landlord3Service).getProxyInstance();
        proxyInstance.rent(3000);
    }
}

适用场景:

被代理的类没有实现接口或者无法实现接口
代理类的代理方法需要进行额外的逻辑,如事务处理等。

六、总结
对于没有实现接口的类,只能使用CGLIB代理
对于实现了接口的类,可以使用JDK代理或者CGLIB代理,如果要求比较高的话,建议使用JDK代理。
对于单个代理类的情况,并且被代理类实现了接口,可以使用静态代理。
对于多个被代理类的情况,建议使用JDK代理或CGLIB代理。

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

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

相关文章

css3实现3D立方体旋转特效源码

源码介绍 CSS3自动旋转正方体3D特效是一款基于css3 keyframes属性制作的图片相册自动旋转立方体特效 效果展示 下载地址 css3实现3D立方体旋转特效代码

Go——下划线

"_"是特殊标识符,用来忽略结果。 1. 下划线在import中 在golang中,import的作用是导入其他package。 import下划线的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候…

Spring Boot如何自定义自己的Starter组件?

一、为什么要自定义starter 在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的 包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一 遍…

SQLiteC/C++接口详细介绍之sqlite3类(三)

上一篇:SQLiteC/C接口详细介绍之sqlite3类(二) 下一篇:SQLiteC/C接口详细介绍之sqlite3类(四) 6.sqlite3_create_module与sqlite3_create_module_v2函数 用于创建自定义SQLite模块。创建自定义模块可以让S…

ChatGPT无法登录,提示我们检测到可疑的登录行为,将阻止进一步的尝试。请与管理员联系

1. 问题描述 之前本来已经连续稳定使用ChatGPT好几个月了,但是今天尝试登录ChatGPT的时候,却提示:我们检测到可疑的登录行为,将阻止进一步的尝试。请与管理员联系。 此外,我还在网上看到了一些相关的消息,…

Kafka的基本介绍以及扩展

文章目录 基本操作新增Topic查询Topic修改Topic删除Topic 生产者和消费者创建生产者创建消费者 Broker扩展Producer扩展Topic、Partition、Message扩展存储策略容错机制 基本操作 新增Topic 指定两个分区,两个副本,replication不能大于集群中的broker数…

HarmonyOS预览功能报错:[webpack-cli] SyntaxError: Unexpected end of JSON input

harmonyos预览功能报错 在使用DevEco Studio写页面&#xff0c;进行预览的时候报错&#xff1a; [Compile Result] [webpack-cli] SyntaxError: Unexpected end of JSON input [Compile Result] at JSON.parse (<anonymous>) [Compile Result] at updateCached…

Fair Data Exchange:区块链实现的原子式公平数据交换

1. 引言 2024年斯坦福大学和a16z crypto research团队 论文 Atomic and Fair Data Exchange via Blockchain 中&#xff0c;概述了一种构建&#xff08;包含过期EIP-4844 blobs的&#xff09;fair data-markets的协议。该论文源自a16z crypto的暑期实习计划&#xff0c;与四名…

第四弹:Flutter图形渲染性能

目标&#xff1a; 1&#xff09;Flutter图形渲染性能能够媲美原生&#xff1f; 2&#xff09;Flutter性能优于React Native? 一、Flutter图形渲染原理 1.1 Flutter图形渲染原理 Flutter直接调用Skia 1&#xff09;Flutter将一帧录制成SkPicture&#xff08;skp&#xff…

2023 收入最高的十大编程语言

本期共享的是 —— 地球上目前已知超过 200 种可用的编程语言&#xff0c;了解哪些语言在 2023 为开发者提供更高的薪水至关重要。 过去一年里&#xff0c;我分析了来自地球各地超过 1000 万个开发职位空缺&#xff0c;辅助我们了解市场&#xff0c;以及人气最高和收入最高的语…

判断对象是否可以被回收:引用计数法,可达性分析,finalize()判定

引用计数法 对象每次被赋值给变量时&#xff0c;该对象的计数1&#xff0c; 若将该变量置为null,则该对象的计数-1 若该对象的计数器为0 &#xff0c;则该对象就会判定为垃圾对象 可达性分析 遍历内存中的所有变量&#xff0c;静态变量&#xff0c;然后将该变量当作GCroot根…

安装配置HBase

HBase集群需要整个集群所有节点安装的HBase版本保持一致&#xff0c;并且拥有相同的配置&#xff0c;具体配置步骤如下&#xff1a; 1. 解压缩HBase的压缩包 2. 配置HBase的环境变量 3. 修改HBase的配置文件&#xff0c;HBase的配置文件存放在HBase安装目录下的conf中 4. 首…

在没有推出硬盘的情况下,重启mac电脑,外接移动硬盘无法加载显示?

一、mac磁盘工具显示未装载 1.打开终端&#xff0c;输入 diskutil list查看当前硬盘列表&#xff0c;大多数时候&#xff0c;可以解决。 二、使用命令行装载硬盘 执行上面命令后&#xff0c;仍不起作用&#xff0c;则手动挂载&#xff0c;在命令行输入如下内容&#xff1a; …

数学建模理论与实践国防科大版

目录 1.数学建模概论 2.生活中的数学建模 2.1.行走步长问题 2.2.雨中行走问题 2.3.抽奖策略 2.4.《非诚勿扰》女生的“最优选择” 3.集体决策模型 3.1.简单多数规则 3.2.Borda数规则 3.3.群体决策模型公理和阿罗定理 1.数学建模概论 1.数学模型的概念 2.数学建模的概…

WAServiceMainContext.js:2 ReferenceError: result is not defined

WAServiceMainContext.js:2 ReferenceError: result is not defined at success (index.js? [sm]:280) at Function.forEach.u.<computed> (WASubContext.js?twechat&s1710205354985&v2.16.1:2) at :22955/appservice/<api request success callback fun…

最好用的流程编辑器bpmn-js系列之基本使用

BPMN&#xff08;Business Process Modeling Notation&#xff09;是由业务流程管理倡议组织BPMI&#xff08;The Business Process Management Initiative&#xff09;开发的一套标准的业务流程建模符号规范。其目的是为用户提供一套容易理解的标准符号&#xff0c;这些符号作…

Android audiotrack尾帧无声

前言 产品一直有用户反馈音频截断问题。在机遇巧合下现学现卖音频知识处理相关问题。 问题描述 我们查看以下简化播放器代码&#xff1a; class AACPlayer(private val filePath: String) {private val TAG "AACPlayer"private var extractor: MediaExtractor? …

nRF52832——串口 UART 和 UARTE 外设应用

nRF52832——串口 UART 和 UARTE 外设应用 UART 和 UARTE 原理UART 功能描述UARTE 功能介绍 应用实例串口打印实例串口输入与回环UART 模式串口中断 UART 和 UARTE 原理 UART 功能描述 串口 UART 也称为通用异步收发器。是各种处理器中常用的通信接口&#xff0c;在 nRF52 芯…

微信小程序实现上下手势滑动切换

效果图 思路 实现一个微信小程序的复合滚动页面&#xff0c;主要通过Swiper组件实现垂直方向的轮播功能&#xff0c;每个轮播项内部使用Scroll-View组件来展示可垂直滚动的长内容&#xff0c;如图片和文本。 代码 <!-- wxml --> <view class"swiper-container…

Spring Boot+Vue前后端分离项目如何部署到服务器

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…