《设计模式》代理模式

news2024/12/29 11:03:52

《设计模式》设计模式的基本原则
《设计模式》单例模式
《设计模式》工厂模式
《设计模式》原型模式
《设计模式》建造者模式
《设计模式》适配器模式
《设计模式》桥接模式
《设计模式》装饰者模式
《设计模式》组合模式
《设计模式》外观模式
《设计模式》享元模式
《设计模式》代理模式

《设计模式》代理模式

  • 1. 基本介绍
  • 2. 静态代理
  • 3. JDK 动态代理
  • 4. CGLIB 动态代理
  • 5. 区别比较

1. 基本介绍

定义

  • 代理模式就是为被访问的目标对象提供一个代理,此时代理对象充当访问对象和目标对象之间的媒介,通过代理对象实现对目标对象的访问。
  • 被代理的对象可以是远程对象、创建开销大的对象以及需要安全控制的对象,Java 中的代理按照代理的生成时机不同分为静态代理和动态代理,静态代理就是在编译期就生成代理对象,而动态代理是在 Java 运行时动态生成,而动态代理又分为 JDK 动态代理和 Cglib 动态代理两种。

代理模式的角色组成

  • 抽象主题类(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题类(Real Subject):实现抽象主题中的具体业务,是代理对象所代表的真实对象,是最终引用的对象。
  • 代理类(Proxy):提供与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式的使用场景

  • 保护代理:控制对一个对象的访问,根据需要为不同的用户提供不同级别的使用权限。
  • 防火墙代理:将浏览器配置成使用代理功能时,防火墙将浏览器的请求转给互联网,当互联网返回数据时,代理服务器再将数据转给浏览器。

代理模式类图如下所示

在这里插入图片描述

2. 静态代理

案例背景

每到节假日来临前后,火车站一定是人流量最大的场所之一。由于现在的网络购票途径非常成熟,因此大家可以在手机上简单操作几下就可以将票买到手了,非常便利。即使是在网络购票的方式出来之前,我记得在我小的时候就经常看见一些门店会贴着火车票的代售点的字样,在那个网络还不发达的年代,这也算是比较方便的购票方式了,总比跑到火车站现场购票方便很多。如果使用代理模式的思想分析这个生活中的场景,那么火车站就可以看作是目标对象,而代售点就是代理对象,我们通过代售点进行买票,火车站和代售点都有售票的功能,“我们”就是访问对象

设计类图如下所示

在这里插入图片描述

SellTickets 接口:公共接口

public interface SellTickets {
    void sell();
}

RailwayStation 类:目标类

public class RailwayStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("售票");
    }
}

Proxy 类:代理类

public class Proxy implements SellTickets{
    private RailwayStation railwayStation = new RailwayStation();
    @Override
    public void sell() {
        System.out.println("代售点收取一些服务费");
        railwayStation.sell();
    }
}

Client 类:访问类

public class Client {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.sell();
    }
}

访问对象 Client 通过代理对象 Proxy 进行购票,同时还增加了收取服务费用的功能。

静态代理的优劣

  • 在不修改目标对象功能的前提下,实现对目标功能扩展。
  • 代理对象需要与目标对象实现相同的接口,会有很多代理类,当接口方法增加时,需要对目标对象和代理对象都进行维护。

3. JDK 动态代理

不同于静态代理,在动态代理中,代理对象不需要实现接口,但是目标对象依然需要实现接口,否则不能使用动态代理。代理对象的生成是利用 JDK 的 API 动态地在内存中构建代理对象。

在 Java API 中提供了一个动态代理类 java.lang.reflect,Proxy,该类不同于上节所说的代理对象的类,而是提供一个创建代理对象的静态方法来获取代理对象。

SellTickets 接口:公共接口

public interface SellTickets {
    void sell();
}

RailwayStation 类:目标类

public class RailwayStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("售票");
    }
}

ProxyFactory 类:代理工厂,创建代理类

public class ProxyFactory {
    private RailwayStation railwayStation = new RailwayStation();

    public SellTickets getProxyObject() {
        // 使用 Proxy 获取代理对象
        SellTickets sellTickets = (SellTickets) Proxy.
                newProxyInstance(railwayStation.getClass().getClassLoader(), // 使用目标对象的类加载器加载代理类
                railwayStation.getClass().getInterfaces(),  // 目标对象实现的接口
                new InvocationHandler() {  // 代理对象的调用处理程序
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理点收取一些服务费");
                        // 执行目标对象
                        Object result = method.invoke(railwayStation, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}

Client 类:访问类

public class Client {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        SellTickets proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
    }
}

为了更好地监控动态代理类在内存中的创建过程,需要使用 Java 诊断工具 arthas 来打印出程序在运行过程中代理类的结构,步骤如下:

  1. 启动 Client 类,使得程序一直保持运行,因为程序运行结束内存便会被释放,则无法观察到动态代理类在程序运行过程中的结构,并打印出代理对象的类名,方便后面 arthas 工具进行查看,Client 类代码如下:
public class Client {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        SellTickets proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();

        System.out.println(proxyObject.getClass());

        while (true) {
            
        }
    }
}
  1. 下载 arthas-jar 工具,点击下载跳转地址
  2. 打开命令行窗口,进入 arthas-boot.jar 所在根目录
  3. 输入命令:java -jar arthas-boot.jar
  4. 找到启动类名称 Client,并输入其对应的序号,如下所示:
    在这里插入图片描述
  5. 加载完成之后,输入命令 jad com.sun.proxy.$Proxy0,等待打印出来的程序运行过程中代理类的结构,如下所示:
    在这里插入图片描述
  6. 完整代码如下:
package com.sun.proxy;

import com.hzz.proxy.dynamicproxy.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 final void sell() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    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.hzz.proxy.dynamicproxy.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);
        }
    }
}
  • 可以看到,代理类 $Proxy0 其实是实现了 SellTickets 接口的,只不过我们是通过 Proxy.newProxyInstance() 方法帮我们实现的,不用像在静态代理中那样显式实现,因此代理类和目标类都要实现相同的接口还是成立的。
  • 此外,从源码中可以看到在 Proxy.newProxyInstance() 方法中创建的匿名内部类中传递了 railywayStation 目标对象,被传递给了 $Proxy0 的父类 Proxy.

根据类的结构可知,动态代理的执行流程大概如下

  1. 首先,在 Client 中通过代理对象调用 sell 方法。
  2. 之后,根据多态性,执行的是代理类 $Proxy0 中的 sell() 方法。
  3. 然后,代理类 $Proxy0 中的 sell() 方法又去调用 InvocationHandler 接口的子实现类对象的 invoke() 方法。
  4. 最后invoke() 方法通过反射执行了目标类 RailwayStation 中的 sell() 方法。

JDK 动态代理的注意事项

  • 使用 JDK 动态代理时,不能代理 privatestatic 方法,代理类和目标类需要实现相同的接口,因为 privatestatic 不能修饰接口。
  • JDK 动态代理只能对接口进行代理,不能对普通类进行代理,因为 JDK 动态代理类生成的 $Proxy0 类的父类为 Proxy 类,Java 中不支持多继承

4. CGLIB 动态代理

从上面两节可以知道,无论是静态代理还是 JDK 动态代理,都需要目标类去实现一个接口,但是有时目标对象就只是一个单独的对象,并没有去实现任何的接口,这时如果还想使用代理模式的话,就可以使用 CGLIB 动态代理。

CGLIB(Code Generation Library) 动态代理

  • CGLIB 通过动态生成一个子类,该子类继承被代理类,重写被代理类的所有非 final 修饰的方法,并在子类中采用方法拦截的技术拦截父类所有的方法调用。
  • CGLIB 代理为 JDK 动态代理提供了很好的补充,作为一个功能强大且高性能的代码生成包,可以为没有实现接口的类提供代理,被广泛应用于 AOP 框架中,实现方法的拦截作用。

CGLIB 的 jar 坐标如下所示:

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

RailwayStation 类:目标类

public class RailwayStation implements SellTickets{
    public void sell() {
        System.out.println("售票");
    }
}

ProxyFactory

public class ProxyFactory implements MethodInterceptor {
    private RailwayStation target = new RailwayStation();

    public RailwayStation getProxyObject() {
        // 创建 Enhancer 对象,类似于 JDK 动态代理的 Proxy 类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建代理对象
        RailwayStation obj = (RailwayStation) enhancer.create();
        return obj;
    }

    /**
     * 重写 intercept 方法,在该方法中会调用目标对象的方法
     * @param o 代理对象
     * @param method 目标对象的方法的 method 实例
     * @param objects 实际参数
     * @param methodProxy 代理对象中的方法的 method 实例
     * @return RailwayStation 
     * @throws Throwable
     */
    @Override
    public RailwayStation intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一些服务费");
        RailwayStation result = (RailwayStation) methodProxy.invokeSuper(o, objects);
        return result;
    }
}

Client 类:访问类

public class Client {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        RailwayStation proxyObject = proxyFactory.getProxyObject();

        proxyObject.sell();
    }
}

CGLIB 代理的注意事项

  • CGLIB 包的底层是使用字节码处理框架 ASM 来转换字节码并生成新的类。
  • 在内存中动态构建子类,代理的类不能为 final,因为被继承的父类如果为常量类那么无法被继承,会报错 java.lang.IllegalArgumentException.
  • 目标对象的方法不能为 private,因为子类无法访问父类的私有方法;目标对象的方法不能为 final,因为子类无法重写父类的不可变方法;目标对象的方法不能为 static,因为静态方法属于类,是不属于对象的。

5. 区别比较

静态代理和动态代理的区别

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

JDK 代理和 CGLIB 代理的区别

  • 如果有接口使用 JDK 动态代理,如果没有接口使用 CGLIB 动态代理。
  • JDK 动态代理使用 Java 反射技术进行操作,在生成类上更高效。
  • CGLIB 使用 ASM 框架直接对字节码进行修改,使用了 FastClass 的特性。在某些情况下,类的方法执行会比较高效

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

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

相关文章

HTML5和CSS3 WEB技术开发

HTML5和CSS3 WEB技术开发 B站视频参考&#xff1a;https://www.bilibili.com/video/BV1H44y1k7ze/ 课程目标&#xff1a; 使用HTML5进行网站布局使用CSS3进行网站美化开发精美的商业网站 第一章 HTML5基础 概念&#xff1a; ​ 网页 &#xff1a;互联网的基础&#xff0c;网…

requests请求库(爬取)

文章目录requests模块链接拼接&#xff08;params参数&#xff09;UA伪装&#xff08;headers参数&#xff09;POST请求页面局部信息爬取&#xff08;GET&#xff09;爬取国家药品监督管理监督总局中基于中华人民共和国化妆品生产许可证相关数据爬取图片爬虫分类通用爬虫&#…

分布式存储从FastDFS切换到Minio

什么是Minio 基于官网的介绍如下&#xff1a;MinIO 是一款高性能、分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。 从官网的介绍可以看出Minio是一款和FastDFS类似的工具&#xff0c;分布式存储系统。目前在使…

运行MAT项目环境配置中出现的问题及参考方案

MAT项目是用于修复图片中缺失的部分&#xff1a;及为图像中缺失的区域产生视觉吸引力和语义适当的内容。 项目链接&#xff1a;GitHub - fenglinglwb/MAT: MAT: Mask-Aware Transformer for Large Hole Image InpaintingMAT: Mask-Aware Transformer for Large Hole Image Inp…

国内有没有可以全职远程办公的程序员工作?

明作为一个曾经靠兼职开发远程办公来赚钱的程序员&#xff0c;既碰到过无良甲方&#xff0c;开发完了不结尾款&#xff0c;最后通过法律手段才解决问题&#xff1b;也接过自称甲方的中介单&#xff0c;耗费心力拿到尾款&#xff0c;最后发现人家拿的钱比自己还多......这一路兼…

方格取数--数字三角形dp问题

项目场景&#xff1a; 线性dp 数字三角形类问题 问题描述 设有 NN 的方格图&#xff0c;我们在其中的某些方格中填入正整数&#xff0c;而其它的方格中则放入数字0。如下图所示&#xff1a; 某人从图中的左上角 A 出发&#xff0c;可以向下行走&#xff0c;也可以向右行走&am…

WebGL及Threejs学习介绍

一、学习背景及实现的效果 这十年来Web得到了飞速的发展&#xff0c;随着WebGL的普及&#xff0c;网页的表现能力越来越强大&#xff0c;网页上已经可以开始做出很多复杂的动画、精美的效果&#xff1b;还能通过WebGL在网页中绘制高性能的3d图形。随着浏览器的性能和网络、带宽…

github实用搜索技巧

github搜索指令教程一. in:根据某个关键词来进行检索1.关键词:name: 项目名称description : 项目描述readme : 项目帮助文档语法 &#xff1a;language:xx(检索什么语言的内容)组合检索二.根据stars||forks||pushed关键字查找1.数量范围: xxx关键词 stars:>或者:<2.区间范…

Pytorch 数据操作

神经网络所处理的数据类型都为tensor类型数据&#xff0c;我们首先需要导入库torch import torch 使用 arange 创建一个行向量 x。这个行向量包含以0开始的前12个整数&#xff0c;它们默认创建为整数。除非额外指定&#xff0c;新的张量将存储在内存中&#xff0c;并采用基于…

进程替换心得

进程替换 1️⃣ 什么是进程替换 1.我们想让子进程不执行父进程部分代码&#xff0c;执行新的程序时我们需要进行进程替换。 ** 程序替换的原理 ** &#xff1a; 将磁盘中的程序&#xff0c;加载入内存结构。重新建立页表映射&#xff0c;谁执行程序替换就程序建立谁的映射(子…

redis 的 java 客户端

Jedis 客户端 1&#xff09;引入依赖&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&…

【Vim】Vim 常用编辑操作

目录 正则表达式 vim 命令 vim的工作模式 撤销修改、重做与保存 光标移动命令 文本插入操作 文本删除操作 文本复制、剪切与粘贴 文本的修改与替换 多窗口操作 正则表达式 简单地说&#xff0c;正则表达式是一种符号表示法&#xff0c;用于识别文本模式。在某种程度上…

Python--栈与队列的相互实现

我们都知道这两个数据结构很相似,但是又有差别, 就好像是对立统一的一样. 栈是一种后进先出的数据结构&#xff0c;元素从顶端入栈&#xff0c;然后从顶端出栈。 队列是一种先进先出的数据结构&#xff0c;元素从后端入队&#xff0c;然后从前端出队。 首先我们都知道用Python的…

leetcode--动态规划问题

动态规划1.基本动态规划 一维&#xff08;1&#xff09;爬楼梯(70)&#xff08;2&#xff09;打家劫舍(198)&#xff08;3&#xff09;等差数列划分(413)2.基本动态规划 二维&#xff08;1&#xff09;最小路径和(64)&#xff08;2&#xff09;01 矩阵(542)&#xff08;3&#…

02SpringCloudAlibaba服务注册中心—Eureka

推荐与对比观看&#xff1a;003SpringCloud---Eureka_gh_xiaohe的博客-CSDN博客 服务提供者 1、 2、pom.xml 3、改yum 4、主启动 5、业务类 测试1&#xff1a; 访问&#xff1a; 服务消费者 1、 2、pom.xml 3、改yum 4、主启动 5、业务类 config controller 测…

【代码封装 center和bounds介绍 Objective-C语言】

一、继续上篇文章的例子 1.刚才我们说了,这个“上下左右”无非就是移动一下frame而已 但是,我们发现,“上下左右”四个功能,我们写了4个方法, 这样做的话,有点儿太繁琐了 所以呢,接下来我们想个办法,能不能把这四个方法,封装一下 封装成1个呢,不要这么多 我们先…

使用Alfred + Gitee搭建免费图床

环境 系统: Mac 工具: Alfred, git, homebrew, pngpaste. 语言: perl 其他: Gitee 工具下载 https://gitee.com/serpmelon/inazuma 思路 使用Gitee仓库作为图床, 使用Alfred工作流简化上传图片流程, 并将上传图片地址转换为markdown格式输出到剪切板中. 使用 复制一张图…

植物大战僵尸:无冷却分析方法

植物大战僵尸这款游戏可以说是很多90后的回忆了&#xff0c;基本上只要是90后或多或少的都接触过&#xff0c;而玩游戏与制作辅助是两个概念&#xff0c;今天我将给大家分享一些游戏辅助方面的制作技巧&#xff0c;来供大家参考。 植物无冷却的实现 根据上节课查找太阳花生产…

基于springboot+Vue的社团管理系统(程序+文档+数据库)

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

(02)Cartographer源码无死角解析-(44) 2D栅格地图→ProbabilityGrid

讲解关于slam一系列文章汇总链接:史上最全slam从零开始&#xff0c;针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解&#xff1a;https://blog.csdn.net/weixin_43013761/article/details/127350885 文末…