【设计模式】代理模式详解

news2024/9/9 1:15:55

1.简介

代理模式是常用的Java设计模式,该模式的特点是代理类与委托类共享相同的接口。代理类主要负责预处理消息、过滤消息、将消息转发给委托类,并在事后处理消息等。代理类与委托类之间通常存在关联关系,一个代理类对象与一个委托类对象关联。代理类对象本身不真正实现服务,而是通过调用委托类对象的相关方法来提供特定的服务。

代理模式主要包括以下角色:

  • 抽象主题(Subject):定义代理类和委托类(RealSubject)的共同接口。这个接口规定了代理类和委托类必须实现的方法,代理类可以通过这个接口来调用委托类的方法。
  • 真实主题(RealSubject):实现抽象主题,定义委托类的操作。它包含了实际的业务逻辑,是客户端实际需要调用的对象。
  • 代理类(Proxy):实现抽象主题,持有对委托类的引用,并在其方法被调用时进行控制。代理类在调用委托类的方法前后可以添加一些额外的功能,如日志记录、权限控制、事务处理等。

2.静态代理

静态代理: 在编译时期确定代理类和目标类的关系,代理类和目标类都要实现同一个接口。

定义一个简单的例子:假如一个租客需要租房子,他可以直接通过**房东(委托类)去租房,也可以经过中介(代理类)**去租房。房东(realsubject)和中介(proxy)都需要实现subject接口实现房子出租。

  1. 确定接口具体行为

首先创建一个Person接口。这个接口是房东和中介的共同接口,租房行为可以被中介代理。

public interface Person {
    // 出租房子
    void hire();
}
  1. 编写委托类业务逻辑

创建一个委托类,实现subject接口,并编写业务逻辑

public class Landlord implements Person{
	// 房东直售
    @Override
    public void hire() {
        System.out.println("出租,收款1000元");
    }
}
  1. 代理类增强方法

创建一个代理类,同样实现subject接口,对委托类的方法进行增强

public class Agency implements Person{

    private final Landlord landlord;

    public Agency(Landlord landlord) {
        this.landlord = landlord;
    }
	// 中介出租,额外收取费用
    @Override
    public void hire() {
        System.out.println("开始办理租房手续");
        landlord.hire();
        System.out.println("额外收取中介费200元");
    }
}
  1. 测试类使用代理对象
public class Main {
    public static void main(String[] args) {
        // 获取代理对象
        Landlord landlord = new Landlord();
        Agency agency = new Agency(landlord);
        // 使用代理方法
        agency.hire();
    }
}

输出结果如下,可以发现对方法进行了增强

3.动态代理

动态代理: 在程序运行时动态生成代理类(subject的实现类)。

相比于静态代理, 动态代理的优势在于其较高的灵活性和代码复用性。同一个动态代理处理器可以代理多个目标对象,而静态代理则需要创建大量的代理类。

在Java中,可以通过JDK和CGLIB实现动态代理。

3.1.JDK动态代理

实现原理

JDK动态代理:在java的java.lang.reflect包下提供了Proxy类和InvocationHandler接口,利用这两个类和接口,可以在运行时动态生成指定接口的实现类

Proxy类就是用来创建一个代理对象的类,在JDK动态代理中我们需要使用其newProxyInstance方法。

public static Object newProxyInstance(
    ClassLoader loader, 
    Class<?>[] interfaces, 
    InvocationHandler h);

这个方法的作用就是创建一个代理类对象,它接收以下三个参数:

  • loader:一个ClassLoader对象,指定哪个ClassLoader将加载生成的代理类。
  • interfaces:一个Interface对象数组,定义代理对象实现的一组接口,代理类可以调用这些接口中声明的所有方法。
  • h:一个InvocationHandler对象,指定代理对象的方法调用将关联到哪个InvocationHandler对象,由它处理实际的方法调用。

InvocationHandler接口提供了一个invoke方法,当代理对象调用方法时,invoke方法会被调用。通过实现这个接口,可以在方法调用前后添加自定义逻辑。

/**
    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
    * method:我们所要调用某个对象真实的方法的Method对象
    * args:指代代理对象方法传递的参数
    */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
代码实现
  1. 确定接口具体行为

这里我们设计两个接口

public interface HouseServiceA {
    // 出租房子
    void hire();
}
public interface HouseServiceB {
    // 转租房子
    void sublet();
}
  1. 编写委托类业务逻辑

委托类实现这两个接口,并且定义具体的业务逻辑

public class HouseServiceImpl implements HouseServiceA, HouseServiceB {

    @Override
    public void hire() {
        System.out.println("出租房子");
    }

    @Override
    public void sublet() {
        System.out.println("转租房子");
    }
}
  1. 编写代理工厂代码

代理工厂负责在运行时动态生成代理类,需要实现InvocationHandler接口重写invoke方法来做方法增强,使用Proxy类创建代理对象。

/**
 * JDK动态代理实现InvocationHandler接口
 */
public class HouseFactory implements InvocationHandler {
    private Object target;

    //定义获取代理对象的方法(将目标对象传入进行代理)
    public Object getJDKProxy(Object target){
        //为目标对象target赋值
        this.target = target;
        //JDK动态代理只能针对实现了接口的类进行代理,因此需要传递接口的class
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class<?>[]{HouseServiceA.class, HouseServiceB.class},
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理开始");
        // 调用invoke方法,result存储该方法的返回值
        Object result = method.invoke(target, args);
        System.out.println("JDK动态代理结束");
        return result;
    }
}
  1. 测试类使用代理对象

在实际使用时,只需要将工厂类生成的代理对象转为需要的代理类,即可实现同时代理多个接口的方法。

public class Main {
    public static void main(String[] args) {
        // 创建代理类工厂
        HouseFactory factory = new HouseFactory();
        // 动态生成接口A的代理类
        HouseServiceA houseProxyA = (HouseServiceA) factory.getJDKProxy(new HouseServiceImpl());
        houseProxyA.hire();
        System.out.println("=====================");
        // 动态生成接口B的代理类
        HouseServiceB houseProxyB = (HouseServiceB) factory.getJDKProxy(new HouseServiceImpl());
        houseProxyB.sublet();
    }
}

返回结果:

3.2.CGLIB动态代理

实现原理

CGLIB动态代理:依赖于ASM下的Enhancer类和MethodInterceptor接口,可以在运行时动态生成目标类的子类

Enhancer类是用来创建代理对象的类。在CGLIB动态代理中,我们需要使用其create方法。

public class Enhancer {
    public Object create();
    // 其他方法
}

这个方法的作用是创建一个代理类对象,通常还需要设置以下几个属性:

  • setSuperclass:设置被代理的目标类,CGLIB通过生成目标类的子类来实现代理。
  • setCallback:设置回调接口,用于处理代理对象的方法调用。

MethodInterceptor接口提供了一个intercept方法,当代理对象调用方法时,intercept方法会被调用。通过实现这个接口,可以在方法调用前后添加自定义逻辑。

/**
    * obj: 代理对象
    * method: 被代理的方法
    * args: 方法的参数
    * proxy: 用于调用父类方法的代理
    */
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;

与JDK动态代理不同,CGLIB代理不需要目标类实现接口。CGLIB通过生成目标类的子类并重写方法来实现代理,因此它可以代理没有实现接口的类。

代码实现
  1. 首先导入依赖
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
  1. 编写委托类业务逻辑(无需实现接口)
public class HouseServiceImpl {

    public void hire() {
        System.out.println("出租房子");
    }

    public void sublet() {
        System.out.println("转租房子");
    }
}
  1. 测试类使用代理对象
public class Main {
    public static void main(String[] args) {
        HouseFactory factory = new HouseFactory();
        HouseServiceImpl cglibProxy = (HouseServiceImpl) factory.getCglibProxy(new HouseServiceImpl());
        cglibProxy.hire();
        System.out.println("=====================");
        cglibProxy.sublet();
    }
}

返回结果:

4.总结

静态代理

实现方式:

  • 由程序员显式编写代理类。代理类在编译期确定,编译前就存在代理类的字节码文件。
  • 需要实现与目标对象相同的接口,且在代理类中显式调用目标对象的方法。

优点:

  • 结构简单,容易理解。

缺点:

  • 每增加一个接口,都需要编写对应的代理类,代码量大,维护成本高。静态代理类在编译期生成,灵活性差。

JDK动态代理

实现方式:

  • 使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。
  • 代理类在运行时动态生成,不需要显式编写代理类。

优点:

  • 代理类在运行时生成,增加了代码的灵活性和可维护性。

缺点:

  • 只能代理实现了接口的类,不能代理没有实现接口的类。

CGLIB动态代理

实现方式:

  • 使用CGLIB(Code Generation Library),依赖ASM字节码生成框架。
  • 代理类在运行时动态生成,不需要显式编写代理类。

优点:

  • 不要求目标类实现接口,可以代理普通的类。
  • 性能通常比JDK动态代理更高,尤其在代理大量方法调用时更为显著。

缺点:

  • 不能代理final类和final方法。

适用场景:

  • 静态代理:需要手动编写代理类,适用于简单的场景,但不够灵活,维护成本高。

  • JDK动态代理:适用于实现了接口的类,代理类在运行时生成,灵活性高,但只能代理接口。

理实现了接口的类,不能代理没有实现接口的类。

CGLIB动态代理

实现方式:

  • 使用CGLIB(Code Generation Library),依赖ASM字节码生成框架。
  • 代理类在运行时动态生成,不需要显式编写代理类。

优点:

  • 不要求目标类实现接口,可以代理普通的类。
  • 性能通常比JDK动态代理更高,尤其在代理大量方法调用时更为显著。

缺点:

  • 不能代理final类和final方法。

适用场景:

  • 静态代理:需要手动编写代理类,适用于简单的场景,但不够灵活,维护成本高。

  • JDK动态代理:适用于实现了接口的类,代理类在运行时生成,灵活性高,但只能代理接口。

  • CGLIB动态代理:适用于没有实现接口的类,性能优于JDK动态代理,但不能代理final类和final方法,且使用复杂度稍高。

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

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

相关文章

TPM管理咨询公司在项目实施过程中提供哪些培训和支持?

在竞争激烈的市场环境中&#xff0c;企业项目的成功实施不仅是技术的较量&#xff0c;更是管理智慧的体现。而TPM管理咨询公司&#xff0c;作为提升企业运营效率与竞争力的专业伙伴&#xff0c;深知在项目推进的每一步中&#xff0c;专业的培训与强大的支持体系对于确保项目顺利…

shell脚本编写、一键安装nginx、条件语句、 检测网段脚本、 打印九九乘法表、

1.shell脚本 1.编写及运行脚本 [root13git ~]# vim hello.sh [root13git ~]# bash hello.sh [root13git ~]# sh hello.sh [root13git ~]# source hello.sh //在当前进程执行 [root13git ~]# chmod x hello.sh [root13git ~]# ./hello.sh 2.一键安装nginx [root13g…

小红书笔记评论采集全攻略:三种高效方法教你批量导出

摘要&#xff1a; 本文将深入探讨如何利用Python高效采集小红书平台上的笔记评论&#xff0c;通过三种实战策略&#xff0c;手把手教你实现批量数据导出。无论是市场分析、竞品监测还是用户反馈收集&#xff0c;这些技巧都将为你解锁新效率。 一、引言&#xff1a;小红书数据…

芋道源码/yudao-cloud二次开发日记(商品sku数据归类为规格属性)

商品的每一条规格和属性在数据库里都是单一的一条数据&#xff0c;从数据库里查出来后&#xff0c;该怎么归类为对应的规格和属性值&#xff1f;如下图&#xff1a; 在商城模块&#xff0c;商品的单规格、多规格、单属性、多属性功能可以说是非常完整&#xff0c;如下图&#x…

Github2024-07-29 开源项目周报Top15

根据Github Trendings的统计,本周(2024-07-29统计)共有15个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目2Java项目2HTML项目2C项目2TypeScript项目2JavaScript项目2非开发语言项目1Vue项目1Go项目1Dart项目1C++项目1Rust项目1Jupyter Note…

项目计划书编制方案(DOC)

项目开发计划包括项目描述、项目组织、成本预算、人力资源估算、设备资源计划、沟通计划、采购计划、风险计划、项目过程定义及项目的进度安排和里程碑、质量计划、数据管理计划、度量和分析计划、监控计划和培训计划等。 软件资料清单列表部分文档&#xff1a; 工作安排任务书…

Temporal(时效)模式01

Andy Carlson, Sharon Estepp, Martin Fowler 著&#xff0c;透明 译 抽象 在面向对象设计中&#xff0c;我们不断使用“对象”&#xff08;object&#xff09;这个词。对象不仅仅用来表现真实世界中存在的物件&#xff0c;它们也被用来表现那些曾经存在但已经消失了的物件&…

关于Docker Engine AuthZ 插件授权绕过漏洞 (CVE-2024-41110)

一、漏洞概述 漏洞名称&#xff1a;Docker Engine AuthZ 插件授权绕过漏洞 &#xff08;CVE-2024-41110&#xff09; 漏洞等级&#xff1a;高危 漏洞描述&#xff1a;DockerEngine是Docker的核心组件&#xff0c;是一 个开源的容器引擎&#xff0c;负责构建、运行和管理容器…

又一新AI搜索工具,OpenAI 推出新的搜索方式 SearchGPT

系列文章目录 每天推荐AI工具系列文章回顾&#xff1a; 选择 haiyi海艺图像生成、LoRA、模型的使用和训练网站 tusiart吐司艺术图像生成、LoRA 模型的使用和训练网站 解锁AI创造力的无限可能&#xff1a;探索Vivago.ai的革命性功能 文章目录 系列文章目录前言一、SearchGPT…

html+css+js前端作业和平精英6个页面页面带js

htmlcssjs前端作业和平精英6个页面页面带js 下载地址 https://download.csdn.net/download/qq_42431718/89595600 目录1 目录2 项目视频 htmlcssjs前端作业和平精英6个页面带js 页面1 页面2 页面3 页面4 页面5 页面6

锐捷RCNA | RGOS日常管理操作和Windows常用命令

RGOS操作系统最主要的三大特性是模块化、安全性、开放性。 RGOS平台登陆方式 平台概述 RGOS全称“锐捷通用操作系统”&#xff0c;即网络设备的操作系统 基于RGOS开发的软件版本目前为11.x&#xff0c;又被称为11.x平台优势 模块化设计&#xff0c;方便运维管理故障隔离&…

[ARC105E] Keep Graph Disconnected题解

题目 考虑加任意一条边时都会输的图的状态&#xff1a;图被分成两个强联通分量&#xff0c;每一个强联通分量都是一个完全图。 也就是说&#xff0c;假设一开始节点 1 1 1 和节点 n n n 不联通&#xff0c;那么还可以加 n ( n − 1 ) 2 − m − c n t 1 ( n − c n t 1 ) \…

78.SAP ME - SAP ME和SAP NetWeaver log files的位置

目录 1.defaultTrace files 内容 文件位置 2.dev_server files 内容 文件位置 3.dev_icm files 内容 文件位置 4.responses.trc files 内容 文件位置 1.defaultTrace files You should always check this log first when any system issue is reported 内容 包含…

经典文献阅读之--GraphAD(端到端自动驾驶的交互场景图)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

什么是项目计划?项目计划如何制定?

做不好项目计划的项目管理&#xff0c;注定会失败&#xff01; 项目计划是帮助管理人员有效实现目标的非常重要的一环&#xff0c;在开始任何项目之前&#xff0c;制定一份详细的计划作为所有参与者的指导性文件非常重要。那么什么是项目计划&#xff1f;项目计划又该如何制定…

springboot集成thymeleaf实战

引言 笔者最近接到一个打印标签的需求&#xff0c;由于之前没有做过类似的功能&#xff0c;所以这也是一次学习探索的机会了&#xff0c;打印的效果图如下&#xff1a; 这个最终的打印是放在58mm*58mm的小标签纸上&#xff0c;条形码就是下面的35165165qweqweqe序列号生成的&…

民大食堂用餐小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商家管理&#xff0c;档口号管理&#xff0c;商家餐品管理&#xff0c;餐品种类管理&#xff0c;购物车管理&#xff0c;订单信息管理 微信端账号功能包括&#xff1a;系统首页&a…

pytorch-迁移学习

目录 1. 宝可梦数据集训练的问题2. 迁移学习3. 迁移学习实现4. 完整代码 1. 宝可梦数据集训练的问题 宝可梦数据总共有1000多张&#xff0c;对于resnet18网络来说数据量是不够的&#xff0c;训练时很容易出现过拟合&#xff0c;那么如何解决这个问题呢&#xff1f; 宝可梦数据…

常见的几种数据标注类型

数据标注是机器学习和人工智能项目中一个至关重要的步骤&#xff0c;它帮助算法理解输入数据中的关键特征。根据不同的应用场景和技术需求&#xff0c;数据标注可以分为多种类型。 以下是一些常见的数据标注类型&#xff1a; 图像标注&#xff1a; 边界框&#xff1a;在物体周…

手撕数据结构---栈和队列的概念以及实现

栈的概念&#xff1a; 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈…