设计模式之代理模式的懂静态代理和动态代理

news2024/11/15 8:48:08

目录

    • 1 概述
      • 1.1 如何实现?
      • 1.2 优点
      • 1.3 缺点
      • 1.4 适用场景
    • 2 静态代理实现
    • 3 JDK 动态代理实现
    • 4 CGlib 动态代理实现
    • 5 总结


1 概述

在这里插入图片描述

代理模式(Proxy Pattern)是一种结构型设计模式,它的概念很简单,它通过创建一个代理对象来控制对原始对象的访问。代理模式主要涉及两个角色:代理角色和真实角色。代理类负责代理真实类,为真实类提供控制访问的功能,真实类则完成具体的业务逻辑。这样,当我们不方便或者不能直接访问真实对象时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

proxymode01

小提示:文中所述真实类,原始类,目标类,是一个意思,都是指被代理角色。

1.1 如何实现?

代理模式的原理是将真实类的功能封装在代理类中,最基本的方式就是创建一个代理类,代理类实现和真实类相同的接口,并且在代理类中引用真实类的实例。这样就可以在不修改真实类代码的情况下,通过代理类来控制对真实类的访问。代理类还可以在调用真实类的方法前后添加一些额外的逻辑,来实现对真实类的访问控制、缓存、日志记录等功能。

实现代理模式有两种方案:静态代理和动态代理。静态代理是指代理类在编译期就已经确定,即需要事先手动编写一个代理类。动态代理则是在运行时动态生成代理类。

动态代理方案有两种实现:其一,通过Java本身自带 java.lang.reflect.Proxy 类来实现动态代理功能。其二,通过额外引入一个开源的高性能代码生成包CGlib来动态生成代理类。二者底层实现上都应用了反射和操作字节码技术。

JDK动态代理是基于接口实现的代理,只能代理实现了接口的类。

CGlib方式是基于继承实现的代理,它不是指真实类需要继承某个父类,而是生成的代理类作为真实类的子类去代理父类,即代理类继承自真实类。这种方式不需实现接口,可以作为JDK代理方式的补充方案。

proxymode03

1.2 优点

  • 代理对象可以隐藏原始对象的实现细节,使得客户端无需了解原始对象的具体实现。
  • 代理对象可以在原始对象的基础上添加额外的功能,例如缓存、安全验证等。
  • 代理对象可以控制对原始对象的访问,保护原始对象不被非法访问。
  • 代理对象可以在客户端和原始对象之间起到中介作用,使得客户端与原始对象之间的耦合度降低。

1.3 缺点

  • 引入代理类会增加系统的复杂性,增加了学习和理解的成本。
  • 由于增加了代理层,导致请求处理速度变慢。

1.4 适用场景

代理模式主要适用于需要控制、增强或隐藏对象访问的场景。适合代理访问的具体场景如下:

  • 远程代理:当客户端需要访问远程对象(位于不同地址空间或网络中)时,可以使用代理模式来隐藏底层网络通信的复杂性,代理对象负责处理网络通信,并将结果返回给客户端。
  • 虚拟代理:当创建和初始化对象的开销很大时,可以使用代理模式延迟对象的实例化,只有在需要真正使用对象时才进行初始化。这样可以提高系统的性能和资源利用率。
  • 安全代理:代理模式可以用于控制对敏感资源的访问,代理对象可以验证客户端的权限或者在访问资源前执行一些安全检查,从而保护真实对象。
  • 日志记录代理:通过代理模式,我们可以在真实对象的方法执行前后进行日志记录,以实现日志记录、调试和性能监测等功能。
  • 延迟加载代理:当需要使用的对象具有较大的开销时,可以使用代理模式来实现延迟加载,只有在真正需要时才加载对象,以节省资源和提高响应速度。
  • 缓存代理:代理模式可以用于实现对象的缓存,当客户端请求某个对象时,代理对象先检查缓存中是否存在该对象,如果存在则直接返回,否则创建新对象并缓存起来,从而提高系统性能。

2 静态代理实现

实现方式比较简单,很容易理解,直接上代码

NO.1 抽象接口:定义视频播放器接口Player

public interface Player {
    void loadVideo(String filename);
    void playVideo(String filename);
}

NO.2 真实类:定义接口实现类VPlayer

public class VPlayer implements Player {
    @Override
    public void loadVideo(String filename) {
        System.out.println("加载MP4视频文件:"+filename);
    }

    @Override
    public void playVideo(String filename) {
        System.out.println("播放MP4视频:"+filename);
    }
}

NO.3 代理类:定义代理类VPlayerProxy,实现同样的接口

public class VPlayerProxy implements Player {

    private Player player;

    public VPlayerProxy(Player player) {
        this.player = player;
    }

    @Override
    public void loadVideo(String filename) {
        player.loadVideo(filename);
    }

    @Override
    public void playVideo(String filename) {
        player.playVideo(filename);
    }
}

NO.4 客户端调用

public class Client1 {
    public static void main(String[] args) {
        //直连方式
        Player vplay=new VPlayer();
        vplay.playVideo("aaa.mp4");
        System.out.println();

        //代理方式
        Player proxy=new VPlayerProxy(vplay);
        proxy.loadVideo("aaa.mp4");
        proxy.playVideo("aaa.mp4");

    }
}

image-20230610194243722

3 JDK 动态代理实现

JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK动态代理通过反射机制实现代理功能,其原理分为以下几个步骤:

  1. 创建实现InvocationHandler接口的代理类工厂:在调用Proxy类的静态方法newProxyInstance时,会动态生成一个代理类。该代理类实现了目标接口,并且持有一个InvocationHandler类型的引用。
  2. InvocationHandler接口:InvocationHandler是一个接口,它只有一个方法invoke。在代理对象的方法被调用时,JVM会自动调用代理类的invoke方法,并将被调用的方法名、参数等信息传递给该方法。
  3. 调用代理对象的方法:当代理对象的方法被调用时,JVM会自动调用代理类的invoke方法。在invoke方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。
  4. invoke方法调用:在invoke方法中,通过反射机制调用目标对象的方法,并返回方法的返回值。在调用目标对象的方法前后,可以执行额外的逻辑。

通用实现代码

public class JDKProxyFactory implements InvocationHandler {

    //需要被代理的对象
    private Object object;

    public JDKProxyFactory(Object object) {
        this.object = object;
    }

    @SuppressWarnings("unchecked")
    public <T> T getProxy(){
        return (T) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),//当前线程的上下文ClassLoader
                object.getClass().getInterfaces(), //代理需要实现的接口
                this); // 处理器自身
    }
	
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
		//进行方法匹配,调用对应方法名的方法
        if ("loadVideo".equals(method.getName())) {
            result=method.invoke(object, args);
        }

        if ("playVideo".equals(method.getName())) {
            System.out.println("前置增强");
            result=method.invoke(object, args);
            System.out.println("后置增强");
        }
        return result;
    }
}

客户端调用

public class Client2 {
    public static void main(String[] args) {
        Player player=new VPlayer();
        Player proxy=new JDKProxyFactory(player).getProxy();
        proxy.loadVideo("aaa.mp4");
        proxy.playVideo("aaa.mp4");

/*      或者
        Player p=new VPlayer();
        Player o = (Player) Proxy.newProxyInstance(
                p.getClass().getClassLoader(),
                p.getClass().getInterfaces(),
                new VPlayerProxyFactory(p)
        );
        o.loadVideo("aaaa.mp4");
*/
    }
}

image-20230610200031639

4 CGlib 动态代理实现

CGLIB(Code Generation Library)是一个基于ASM(Java字节码操作框架)实现的代码生成库,它可以在运行时动态生成目标类的子类作为代理类,并覆盖其中的方法来实现代理功能。与Java自带的JDK动态代理不同,CGlib动态代理可以代理没有实现接口的类。其原理分为以下几个步骤:

  1. 创建Enhancer对象:Enhancer是CGLIB库中用于动态生成子类的主要类。通过创建Enhancer对象并设置需要代理的目标类、拦截器等参数,可以生成一个代理类。
  2. 设置回调拦截器:在生成代理类时,需要指定拦截器。拦截器是实现代理逻辑的关键,它会在代理类的方法被调用时拦截调用,并执行相应的逻辑。在CGLIB中,拦截器需要实现MethodInterceptor接口。
  3. 创建代理对象:通过调用Enhancer对象的create方法,可以生成一个代理对象。代理对象会继承目标类的方法,并且在调用代理对象的方法时会先调用拦截器的intercept方法,再执行目标方法。
  4. 调用代理对象:通过调用代理对象的方法,会触发拦截器的intercept方法。在intercept方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。

单独定义一个没有接口的真实类APlayer

//音频播放器
public class APlayer {
    public void loadAudio(String filename) {
        System.out.println("加载MP3音频文件:"+filename);
    }

    public void playAudio(String filename) {
        System.out.println("播放MP3:"+filename);
    }
}

通用实现代码

public class CglibProxyFactory implements MethodInterceptor {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz) {
        Enhancer en = new Enhancer();
        //设置代理的父类
        en.setSuperclass(clazz);
        //设置方法回调
        en.setCallback(this);
        //创建代理实例
        return (T)en.create();
    }

    @Override
    //参数中的object是目标对象,method和args是目标对象的方法和参数,methodProxy是方法代理
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;

        if ("loadAudio".equals(method.getName())) {
            //通过继承的方法实现代理,因此这里调用invokeSuper
            result = methodProxy.invokeSuper(object, args);
        }
        if ("playAudio".equals(method.getName())) {
            result = methodProxy.invokeSuper(object, args);
        }
        return result;
    }
}

客户端调用

public class Client3 {
    public static void main(String[] args) {
        APlayer aplayer=new APlayer();
        APlayer proxy = new CglibProxyFactory().getProxy(aplayer.getClass());
        //验证代理类的父类
        System.out.println("代理类的父类:"+proxy.getClass().getSuperclass().getSimpleName());
        System.out.println();

        proxy.loadAudio("荷塘月色.mp3");
        proxy.playAudio("荷塘月色.mp3");
    }
}

image-20230610201932707

5 总结

代理模式可以在不修改真实类代码的情况下,实现对真实类的访问控制、性能优化等功能。Java 中有两种实现代理模式的方法:静态代理和动态代理。静态代理需要在编译之前手动编写代理类,而动态代理可以在运行时动态生成代理类。

其中动态代理又分为JDK动态代理和CGlib动态代理,一般情况下我们使用前者,当代理类没有接口时选用后者作为前者的补充方案。代理模式的优点包括降低系统耦合度、增强代码可扩展性、提高系统安全性和简化接口,但它也会增加系统的复杂性和可能影响性能。在需要控制访问、远程代理、功能增强等场景下,代理模式是一个很好的选择。

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

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

相关文章

便捷又炸街!Mate 60的智感支付,是如何做到快速又安全的?

扫码支付已成为线下消费的主流付款方式&#xff0c;平时出门&#xff0c;手机一带&#xff0c;钱包拜拜&#xff01; 以微信支付为例&#xff0c;正常线下支付&#xff0c;手机解锁状态下&#xff1a; 第一步&#xff1a;找到微信APP&#xff1b; 第二步&#xff1a;打开右上…

9月15日作业

Qt代码 #include "mywnd.h"//构造函数的定义 mywnd::mywnd(QWidget *parent): QWidget(parent) //显性调用父类的有参构造完成对子类从父类继承下来成员的初始化工作 {//窗口设置this->resize(QSize(500, 433));this->setWindowTitle("Widget&quo…

MySQL-Linux安装、卸载:

MySQL8.0.26-Linux版安装 1. 准备一台Linux服务器 云服务器或者虚拟机都可以; Linux的版本为 CentOS7; 2. 下载Linux版MySQL安装包 https://downloads.mysql.com/archives/community/ 3. 创建目录&#xff0c;上传MySQL安装包 /usr/local/src/MySql/ 4. 解压 # 进入到…

软件测试面试必备,一文带你彻底掌握接口测试

一、什么是接口测试&#xff1f; 所谓接口&#xff0c;是指同一个系统中模块与模块间的数据传递接口、前后端交互、跨系统跨平台跨数据库的对接。而接口测试&#xff0c;则是通过接口的不同情况下的输入&#xff0c;去对比输出&#xff0c;看看是否满足接口规范所规定的功能、…

Python vs C#:首先学习哪种编程语言最好?

进入编码可能很困难。 最艰难的部分? 决定先学什么语言。 当谈到 Python 与 C# 时,可能很难知道在您的决定中要考虑哪些因素。 我们为您提供了有关这些全明星编程语言的所有信息。 什么是 C#? 自 2000 年作为 Microsoft Visual Studio 的一部分开发 C# 以来,它一直是开发人…

【CSS】画个三角形或圆形或环

首先通过调整边框&#xff0c;我们可以发现一些端倪 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><style>.box{width: 150px;height:150px;border: 50px solid black;}</style&g…

Python编程语言学习笔记

目录 1 书写格式1.1 程序框架格式1.1 注释1.2 保留字 2 数据2.1 整数类型2.2 浮点类型2.3 复数类型2.4 数值运算符2.5 数值运函数2.6 数值类型转换函数2.7 math 库2.8 字符串2.8.1 字符串的表示2.8.2 字符串的区间访问2.8.3 字符串操作符2.8.4 字符串操作函数 2.9 字符串类型的…

[杂谈]-二进制到十进制转换

二进制到十进制转换 文章目录 二进制到十进制转换1、概述2、十进制计数系统3、二进制计数系统4、二进制到十进制转换示例5、十进制到二进制转换6、二进制数的名称和前缀7、总结 1、概述 二进制数在数字系统和计算机中用于计算数据。 此外&#xff0c;数字系统之间的数据传输或…

【iOS】浅析static,const,extern关键字

文章目录 前言一、staticstatic修饰局部变量static修饰全局变量总结 二、const三、extern声明全局变量声明函数在头文件中使用总结 前言 笔者本周在学习单例模式时&#xff0c;用到了static关键字&#xff0c;特此总结博客记录学习static&#xff0c;const&#xff0c;extern关…

文件名批量重命名与翻译的实用指南

随着电脑使用的普及&#xff0c;我们经常需要处理大量的文件和文件。有时&#xff0c;我们可能希望将文件的名称进行批量修改&#xff0c;并且还希望将这些名称翻译成其他语言&#xff0c;以便更好地管理和查找文件。在这篇文章中&#xff0c;我们将介绍一种实用的技巧&#xf…

【面试经典150 | 数组】删除有序数组中的重复项 II

文章目录 写在前面Tag题目解读题目来源解题思路方法一&#xff1a;原地操作 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等…

Hadoop生态圈中的数据同步工具SQOOP

Hadoop生态圈中的数据同步工具SQOOP 一、sqoop的概念二、sqoop的核心功能1、数据导入import2、数据导出export 三、sqoop的底层实现四、sqoop的安装和部署五、sqoop的基本操作1、sqoop查看RDBMS中有哪些数据库2、sqoop查看某一个数据库下有哪些数据表3、通过sqoop执行sql语句 …

SOLIDWORKS有限元分析怎么做?

在许多专业领域中&#xff0c;尤其是在机械行业&#xff0c;为了缩短开发周期&#xff0c;设计完成后通常需要进行仿真分析。通常情况下&#xff0c;大家会使用专业的有限元仿真软件如ANSYS进行仿真分析。但其实&#xff0c;SOLIDWORKS软件因其简单易用的制图功能以及内置的专用…

bigcache

bigcache 介绍 借用下图片&#xff0c;实际上&#xff0c;这张图还不太全&#xff0c;queueItem 中&#xff0c;entrydata的最前端 8 字节是时间戳&#xff0c;用来计算过期时间的。 bigcache 的思想主要有以下几点&#xff1a; 大并发下&#xff0c;尽量减少同步带来的时…

3 分钟,带你了解低代码开发

一、低代码平台存在的意义 传统软件开发交付链中&#xff0c;需求经过3次传递&#xff0c;用户→业务→架构师→开发&#xff0c;每一层传递都可能使需求失真&#xff0c;导致最终交付的功能返工。 业务的变化促使软件开发过程不断更新、迭代和演进&#xff0c;而低代码开发即是…

2023!6招玩转 Appium 自动化测试

Appium是个什么鬼 Appium是一个移动端的自动化框架&#xff0c;可用于测试原生应用&#xff0c;移动网页应用和混合型应用&#xff0c;且是跨平台的。可用于IOS和Android以及firefox的操作系统。原生的应用是指用android或ios的sdk编写的应用&#xff0c;移动网页应用是指网页…

合约谈崩,3大汽车厂工人集体罢工 | 百能云芯

周五&#xff0c;美国联合汽车工会&#xff08;UAW&#xff09;在底特律三大汽车制造商通用汽车、福特汽车和克莱斯勒母公司Stellantis旗下的各一家工厂同步举行了罢工&#xff0c;可能因工资和就业保障问题引发一场代价高昂且长时间的对峙。 协商签订新劳资协议的最后期限已过…

【多线程】死锁 详解

死锁 一. 死锁是什么二. 死锁的场景1. 一个线程一把锁2. 两个线程两把锁3. N 个线程 M 把锁 三. 死锁产生的四个必要条件四. 如何避免死锁 一. 死锁是什么 死锁是这样一种情形&#xff1a; 多个线程同时被阻塞&#xff0c;因为每个进程都在等其他线程释放某些资源&#xff0c;…

在波卡区块链学院学习 Web3 是种什么体验?

成立于 2022 年的 Polkadot Blockchain Academy&#xff08;波卡区块链学院&#xff0c;以下简称 PBA&#xff09;是由波卡生态成立的一个深入的、开创性的区块链课程&#xff0c;致力于帮助 Web3 创新者和未来的顶级编程人员实现他们的想法和抱负。 波卡区块链学院由波卡创始…

LeetCode 1222. 可以攻击国王的皇后【数组,模拟】1391

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…