肝一肝设计模式【七】-- 代理模式

news2024/11/28 17:59:24

系列文章目录

肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门
肝一肝设计模式【三】-- 原型模式 传送门
肝一肝设计模式【四】-- 建造者模式 传送门
肝一肝设计模式【五】-- 适配器模式 传送门
肝一肝设计模式【六】-- 装饰器模式 传送门


文章目录

  • 系列文章目录
  • 前言
  • 一、什么是代理模式
  • 二、静态代理
  • 三、动态代理
    • 1. JDK动态代理
    • 2. CGLib动态代理
  • 写在最后


前言

本节我们继续分析设计模式中的结构型模式,前文中我们已经分析了适配器模式和装饰器模式,本节我们来学习一下——代理模式。


一、什么是代理模式

代理模式(Proxy Pattern),用于在对象之间提供间接访问,在代理模式中,代理对象充当了原始对象的代表,以控制对原始对象的访问。

代理模式的主要角色:

  • 抽象主题(Subject):定义了代理类和原始类之间的公共接口,以便代理类可以替代原始类。抽象主题通常是一个接口或抽象类,其中定义了原始对象和代理对象需要实现的方法。

  • 具体主题(Real Subject):是代理模式中的原始对象,代理类所代表的对象。具体主题实现了抽象主题中定义的接口,是真正执行业务逻辑的对象。

  • 代理(Proxy):代理是客户端访问具体主题的中介。代理对象与具体主题实现相同的接口,并保存具体主题的引用。当客户端向代理对象发送请求时,代理对象会将请求转发给具体主题,并可以在请求前后添加额外的逻辑。

概念了解了以后,其实不难理解,生活当中就有很多代理模式的样例,举个栗子,韩梅梅和李雷是同学,韩梅梅饿了但又不想自己出去吃饭,就想让李雷去买回来。
在这里插入图片描述

这里李雷就相当于是代理角色。

二、静态代理

代理模式又分为静态代理和动态代理,先说静态代理

写下代码:

先定义一个顶层接口,定义买饭这件事

public interface IPerson {
	void findFood();
}

韩梅梅饿了要吃饭

public class HanMeiMei implements IPerson {
	@Override
	public void findFood() {
		System.out.println("韩梅梅饿了想找点吃的");
	}
}

但韩梅梅有点懒,想让李雷帮忙买回来

public class LiLei implements IPerson {
	
	private HanMeiMei hanMeiMei;
	
	public LiLei(HanMeiMei hanMeiMei) {
		this.hanMeiMei = hanMeiMei;
	}

	@Override
	public void findFood() {
		System.out.println("李雷接到韩梅梅的电话");
		hanMeiMei.findFood();
		System.out.println("李雷帮韩梅梅买点吃的回来");
	}
}

测试一下:

public class Test {
    public static void main(String[] args) {
        LiLei liLei = new LiLei(new HanMeiMei());
        liLei.findFood();
    }
}

静态代理是在编译时就已经确定代理关系的代理模式。它需要为每个原始对象编写一个代理类,代理类与原始类实现相同的接口,以便可以通过代理类访问原始对象。在代理类中,可以通过调用原始对象的方法来实现对原始对象的访问,并可以在方法前后添加额外的逻辑。

三、动态代理

上述场景里,韩梅梅的主要诉求是饿了想吃饭,如果当时李雷没联系上,就还得联系别人,代码角度就还需要新增一个代理类,这样的话显然使用静态代理就不太适合了,目的是买到食物,具体谁来买其实并不重要,这就引申出来动态代理的概念。

在Java中,目前普遍使用的是JDK动态代理和CGLib动态代理

1. JDK动态代理

JDK动态代理是指使用Java内置的反射机制来动态生成代理对象的一种代理模式实现方式。

JDK动态代理需要满足以下两个条件:

  • 被代理类必须实现至少一个接口。

  • 代理类必须实现InvocationHandler接口。

InvocationHandler接口中只定义了一个方法invoke(),这个方法会在代理对象调用方法时被自动调用。在invoke()方法中,我们可以根据方法名和参数类型等信息,决定是否要将方法调用转发给被代理对象,或者在调用前后添加一些额外的逻辑。

我们来修改下代码:

首先先修改一下代理类

public class WaiMai implements InvocationHandler {

	private HanMeiMei hanMeiMei;

    public WaiMai(HanMeiMei hanMeiMei) {
        this.hanMeiMei = hanMeiMei;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("拿起手机");
        Object result = method.invoke(hanMeiMei, args);
        System.out.println("在外卖平台下单,小哥开始配送");
        return result;
    }
}

测试一下:

public class Test {
    public static void main(String[] args) {
        HanMeiMei hanMeiMei = new HanMeiMei();
        IPerson proxyPerson = (IPerson) Proxy.newProxyInstance(
            hanMeiMei.getClass().getClassLoader(),
            hanMeiMei.getClass().getInterfaces(),
            new WaiMai(hanMeiMei)
        );
        proxyPerson.findFood();
    }
}

newProxyInstance()方法会动态生成一个代理类,该方法有三个参数:

  • ClassLoader:生成一个类, 这个类也需要加载到方法区中, 因此需要指定ClassLoader来加载该类
  • Class[] interfaces:要实现的接口
  • InvocationHandler:调用处理器

2. CGLib动态代理

CGLib动态代理是指使用CGLib库来动态生成代理对象的一种代理模式实现方式。
相比于JDK动态代理,它可以代理没有实现任何接口的类,因为它是通过继承被代理类来生成代理对象的。

CGLib动态代理的实现过程是:通过ASM字节码框架直接将代理对象类的class文件加载到JVM中,修改其字节码生成子类,子类重写父类中的方法,并在重写的方法中增加了我们定义的逻辑。最后,生成一个新的代理对象子类的class文件,然后通过反射机制来创建代理对象。

修改下代码:

首先修改一下代理类,不在需要实现顶层接口

public class HanMeiMei {
	public void findFood() {
		System.out.println("韩梅梅饿了想找点吃的");
	}
}

先新增一个实现MethodInterceptor接口的实现类

public class WaiMai implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("拿起手机");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("在外卖平台下单,小哥开始配送");
        return result;
    }
}

测试一下:

public class Test {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HanMeiMei.class);
        enhancer.setCallback(new WaiMai());

        HanMeiMei proxyHanMeiMei = (HanMeiMei) enhancer.create();
        proxyHanMeiMei.findFood();
    }
}

CGLib动态代理,可以选择使用反射调用或者FastClass机制调用,默认情况下会使用反射调用,如果需要使用FastClass机制调用,则需要通过设置Enhancer类的useFastClass属性来开启。

使用FastClass机制:

public class Test {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HanMeiMei.class);
        enhancer.setCallback(new WaiMai());

		enhancer.setUseFactory(false);  // 禁用缓存,强制使用FastClass机制
        enhancer.setUseFastClass(true); // 开启FastClass机制

        HanMeiMei proxyHanMeiMei = (HanMeiMei) enhancer.create();
        proxyHanMeiMei.findFood();
    }
}

使用FastClass机制调用时,CGLib会通过ASM字节码框架生成一个FastClass类,该类中包含被代理类中方法的索引和对应的方法调用代码,这样在调用代理类的方法时,就可以直接使用FastClass类中对应方法的调用代码,避免了反射调用的开销。
需要注意的是,使用FastClass机制调用虽然能够提高代理类的性能,但也会增加代理类生成的时间和内存开销。

上文所描述的是CGLib 3.0.0 版本之前,想开启 FastClass 机制,需要手动调用setUseFastClass()方法来设置。
CGLib 3.0.0 版本开始默认启用 FastClass 机制,无需调用setUseFastClass()方法。


写在最后

代理模式的基本思想是创建一个代理对象,该代理对象与原始对象具有相同的接口,以便可以替换原始对象。当客户端向代理对象发送请求时,代理对象会将请求转发给原始对象,同时可以在请求前后添加额外的逻辑。这种方式可以隐藏原始对象的复杂性,并提供更加简单和易用的接口。

代理模式的优点:

  • 解耦原有对象,代理对象与原有对象之间的耦合度降低,原有对象可以专注于自己的业务逻辑,而代理对象则负责其他方面的操作。
  • 对原有对象进行增强,代理对象可以在调用原有对象方法前后进行一些操作,例如日志记录、缓存处理等,从而增强了原有对象的功能。

静态代理的优点:

  • 在编译时进行类型检查,避免了运行时出现类型错误的风险
  • 提供更好的性能,因为代理类在编译时就已经生成,不需要在运行时动态生成代理对象

静态代理的缺点:

  • 需要为每个原始对象编写一个代理类,如果原始对象的接口发生变化,代理类也需要相应地进行更新

动态代理的优点:

  • 可以动态地生成代理对象,避免了静态代理中需要为每个原始对象编写代理类的麻烦
  • 可以支持对不同的原始对象进行代理,并可以在运行时动态地添加或删除代理对象

动态代理的缺点:

  • JDK动态代理和CGLib动态代理(反射调用)需要通过反射机制动态生成代理类,可能会降低一些性能,CGLib动态代理(FastClass调用)虽然避免了反射调用的开销,但会增加代理类生成的时间和内存开销,同样会影响性能
  • 只能代理公共方法,不能代理私有方法和final方法

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

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

相关文章

JavaWeb:Cookie、Session、JSP、JavaBean、MVC 三层架构

文章目录 JavaWeb - 03一、Cookie1. Cookie 应用2. 注意点 二、Session三、JSP1. 概述2. JSP 基础语法和指令(了解)3. 内置对象及作用域4. JSP 标签、JSTL 标签 四、JavaBean五、MVC 三层架构1. 之前的架构2. 现在的 MVC 三层架构 注意: Java…

Feign和OpenFeign

1.Feign和OpenFeign的关系 Feign Feign是一个声明式的Web服务客户端(Web服务客户端就是Http客户端),让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。 Feign是Spring Cloud组件中一个轻量级RESTful的H…

MySQL基础学习---7、子查询

子查询 子查询指的是一个查询语句嵌套在另一个查询语句内部的查询(从MySQL4.1开始引入)。1、子查询的基本使用 语法格式:select select_listfrom tablewhere expr operator(select select_listfrom table); 说明:1、子查询&…

【终极解决方案】IDEA maven 项目修改代码不生效。

【终极解决方案】IDEA maven 项目修改代码不生效。 文章目录 【终极解决方案】IDEA maven 项目修改代码不生效。1、项目问题描述2、可能的解决方案3、分析原因4、解决方案5、参考文献 1、项目问题描述 遇到一个非常奇怪的问题,修改了一个基于maven搭建的SSM项目&am…

learn_C_deep_9 (汇编角度理解return的含义、const 的各种应用场景、volatile 的基本理解与实验证明)

目录 return 关键字 const 关键字 const 修饰的只读变量 - - - 不可直接被修改! const修饰的变量,可以作为数组定义的一部分吗? const只能在定义的时候直接初始化,不能二次赋值。为什么? const修饰指针 volatil…

opencv remap 像素重映射

remap()函数的输入是一个源图像和一个映射矩阵。映射矩阵包含了每个像素的新坐标,用于指定每个像素在输出图像中的位置。 假设原始图像中的一个像素的坐标为 ( x , y ) (x,y) (x,y),它在输出图像中的新坐标为 ( x ′ , y ′ ) (x,y) (x′,y′)。为了计算…

Linux Python Openpyxl xlsx转html

目录 Excel转Html 示例 函数 Openpyxl知识点 其他Excel解析库问题 Excel转Html 示例 函数 import openpyxlfrom openpyxl.styles import Border from openpyxl.styles import Font from openpyxl.styles import Side from openpyxl.styles import Alignment from openpy…

ChipScope 使用问题和解决方案

背景介绍 我最近在学习FPGA开发技术,用杜勇老师的《Xinlinx FPGA数字信号处理设计》一书,按照书中的例子,对 CXD301 开发板进行ADC、DAC示例的调试,使用 ChipScope 软件进行在线逻辑分析。遇到了下面的问题,并给出了解…

【软考数据库】第十章 系统开发与运行

目录 10.1 系统实施 10.1.1 信息系统生命周期 10.1.2 能力成熟度模型 10.1.3 软件过程开发模型 10.1.4 信息系统开发方法 10.1.5 系统分析与设计 10.1.6 结构化开发 10.2 系统测试 10.2.1 测试原则和方法 10.2.2 测试阶段 10.2.3 测试用例设计 10.2.4 调试 10.2.…

现代化个人博客系统 ModStartBlog v7.3.0 首页热门博客,UI优化调整

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用,支持后台一键快速安装,让开发者能快的实现业务功能开发。 系统完全开源,基于 Apache 2.0 开源协议。 功能特性 丰富的模块市场,后台一键快速安装 …

GETOPT函数详解

一、目的 相信第一次接触linux的小伙伴在使用命令行时肯定有这样的疑惑,命令行各种各样的选项和选项参数是怎样实现的(各个命令的选项的含义可以通过man手册查看)。 二、介绍 在正式介绍之前,我们先来看一下命令行选项的使用&…

数据库原理+openGauss

文章目录 0. 准备工作0.1 实验环境0.2 主要内容0.3 学习资源 1. 安装部署实验(实验一)1.1 下载VirtualBox1.2 安装VirtualBox1.3 镜像文件导入1.4 启动虚拟机1.5 数据库使用1.6 数据库基本操作 2 表&模式(实验二)2.1 创建模式…

jmeter如何测试一个post请求(发送json请求报文)

目录 1.配置测试计划1.1.创建POST的HTTP请求取样器(模拟POST请求)1.2.创建HTTP信息头管理器 2.执行压测并查看结果 jmeter如何测试一个发送json报文的post请求. 更详细的配置请参考另外一篇博文: jmeter如何测试一个get请求 1.配置测试计划 …

独立站运营必做的6项工作内容

独立站的运营工作内容比较繁琐和全面,主要包括以下几个方面: 一、网站策划和设计 定义网站目标和定位制定网站规划和设计方案确定网站主题、颜色和页面布局确定网站的核心功能和用户体验设计网站的Logo和其他品牌元素 二、网站建设和维护 选择网站主…

React基础入门【一】

官方文档:https://react.docschina.org/ 说明 本文总结自尚硅谷课程。学习本教程之前,最好具备vue的基础知识,明白虚拟DOM、jsx这些前置知识。接下来,我们通过一个简单的示例来展示react的使用。 注意:入门的学习不…

【Atlas 200】华为昇腾Atlas 200加速模块RC场景无法启动卡在Start to jump Linux kernel

问题现象 RC场景下,装有华为昇腾Atlas 200加速模块的开发板无法启动系统。 将制作好系统的SD卡插入开发板,0号串口输出的日志卡在Start to jump Linux kernel,之后没有更多输出。 可能原因 一种比较大的可能是——在制作系统时&#xff0c…

Mac终端代理

1.打开代理查看代理端口号 打开设置,点击网络,点击详细信息,点击代理查看代理端口号。 2.修改环境变量 1)终端输入下面命令 vim .zshrc 2)在.zshrc文件里添加下面两段内容(注意:7980为端口号…

力扣206反转链表:代码实现+图文全解+方法总结(四种方法)

文章目录 第一部分:题目描述第二部分:题解2.1 方法一:生成新节点到新链表2.2 方法二:复用旧节点到新链表🍀 面向过程式思想方法🍀 面向对象式思想方法 2.3 方法三:递归2.4 旧链表中移动旧节点 第…

02:MYSQL---DML

目录 1:介绍 2:DML数据操作 1:介绍 DML英文全称是Data Manipulation Lanquage(数据操作语言),用来对数据库中表的数据记录进行增删改操作。 添加数据 :insert 修改数据:update 删除数据:delete 2:DML数据操作 给指定字段添加数据 insert into 表名(字段名1,…

segment-anything本地部署使用

前言 Segment Anything Model(SAM)是一种先进的图像分割模型,它基于Facebook AI在2020年发布的Foundation Model3,能够根据简单的输入提示(如点或框)准确地分割图像中的任何对象,并且无需额外训…