设计模式——代理模式(静态代理、JDK动态代理、CGLIB动态代理)

news2024/11/16 7:28:22

是什么?

如果因为某些原因访问对象不适合,或者不能直接引用目标对象,这个时候就需要给该对象提供一个代理以控制对该对象的访问,代理对象作为访问对象和目标对象之间的中介;

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理,静态代理代理类在编译时期就生成,而动态代理代理类则是在Java运行时动态生成的,而动态代理又分为JDK动态代理和CGLIB动态代理两种;

结构

抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法;

真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象;

代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或者扩展真实主题的功能;

实现

我们现在实现一个租客通过中介买房的例子;

静态代理

抽象主题类

public interface Retending {
    void retend();
}

具体主题类

public class TomsonYipin implements Retending{
    //汤臣一品房东
    @Override
    public void retend() {
        System.out.println("出售汤臣一品");
    }
}

代理类

public class AgencyProxy implements Retending{
    //房产中介
    private final TomsonYipin tomsonYipin=new TomsonYipin();  //聚合真实主题类
    @Override
    public void retend() {
        System.out.println("收取中介费用");
        tomsonYipin.retend();
    }
}

租客

public class Tenant {
    public static void main(String[] args) {
        //模拟租客向中介租房
        //直接访问对象不能是房东,而是中介;
        AgencyProxy proxy = new AgencyProxy();
        proxy.retend();
    }
}

上面的代码AgencyProxy类其实就是就是起到一个中介的角色,将具体主题类和用户类相互隔离开,同时也对具体主题的方法进行了一点增强;

JDK动态代理

Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法:newProxyInstance方法来获取代理对象;

抽象主题类

public interface Retending {
    void retend();
}

具体主题类

public class TomsonYipin implements Retending {
    //汤臣一品房东
    @Override
    public void retend() {
        System.out.println("出售汤臣一品");
    }
}

代理工厂获取代理对象

public class AgencyProxyFactory {
    private final TomsonYipin tomsonYipin = new TomsonYipin();

    //创建代理对象并返回
    public Retending getObjectProxy(){
       Retending retending = (Retending)Proxy.newProxyInstance(tomsonYipin.getClass().getClassLoader(),
               tomsonYipin.getClass().getInterfaces(),
        new InvocationHandler() {
           /**
            * Object proxy:它其实就是我们外面获取到的retending对象,基本不用
            * Method method:对接口中的方法进行封装的method对象;
            * Object[]args:调用方法的实际参数
            * return:就是抽象主题定义的方法的返回值
            * */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("中介收取服务费");
                //执行目标对象的方法=>通过反射
                Object obj = method.invoke(tomsonYipin, args);
                return obj;
            }
        });
       return retending;
    }
}

租客

public class Tenant {
    public static void main(String[] args) {
        //模拟租客向中介租房
        AgencyProxyFactory proxyFactory = new AgencyProxyFactory();
        Retending proxy = proxyFactory.getObjectProxy();
        proxy.retend();
    }
}

 分析

1.首先我们没有自己再去构造代理类了,而是调用Proxy中的newProxyInstance方法来帮我们创建代理对象

2.其中我们提供类加载器,类所实现的接口,以及一个InvocationHandler接口对象

我们new出该接口对象,需要以匿名内部类的形式去实现该接口中的invoke方法

这个方法非常关键,当用户在外面获取到代理对象之后,调用对象中的方法时,其实就是相当于调用的这个invoke方法,也就是说用户在外面调用retending.retend()方法的时候,其实就是在调用该invoke方法;

3.而我们现在在invoke方法中去调用具体主题对象中的方法,就只需要使用method中的invoke方法即可,因为这个method对象就是对接口中的方法进行了封装;

思考(重点)

我们写的这个AgencyProxyFactory是代理类吗?

这个类并不是代理模式中所说的代理类,它只是我们写的一个工厂类,我们在这个工厂类中获取的是代理对象,而代理类是程序帮我们在运行过程中动态的在内存中生成的类

在代理类$Proxy0中它实现了Retending接口,这也就印证了我们之前说的真实类和代理类实现同样的接口;

代理类$Proxy0将我们提供了的匿名内部类对象传递给了父类;

 

 执行流程

1.在测试类中我们通过代理工厂去获取了代理对象,并调用它的retend()方法;

2.根据多态的特性,实际执行的是代理类$Proxy0中的retend()方法;

3.代理类$Proxy0中的retend()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法(也就是我自己new的那个InvocationHandler对象,在匿名内部类中实现的invoke方法);

4.invoke方法通过反射执行了真实对象所属类中的retend()方法

CGLIB动态代理

同样是上面的案例,但这次我们没有定义Retending接口,而只有TomsonYipin类(或者是以继承的方式),那这个时候显然就不能使用JDK动态代理了,因为JDK动态代理要求必须定义接口,对接口进行代理;

CGLIB是一个功能强大,高性能的代码生成包,它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充;

添加依赖

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

具体主题类

public class TomsonYipin {
    //汤臣一品房东
    public void retend() {
        System.out.println("出售汤臣一品");
    }
}

代理工厂获取代理对象

public class CGLIBProxyFactory implements MethodInterceptor {
    //声明目标对象
    private final TomsonYipin tomsonYipin = new TomsonYipin();
    //CGLIB代理工厂用来获取代理对象
    public TomsonYipin getProxy(){
        //创建Enhancer对象,它类似于JDK动态代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(TomsonYipin.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TomsonYipin proxy = (TomsonYipin) enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //调用目标对象的方法
        System.out.println("CGLIB中介收取服务费");
        Object obj = method.invoke(tomsonYipin, objects);
        return obj;
    }
}

租客

public class Tenant {
    public static void main(String[] args) {
        CGLIBProxyFactory proxyFactory = new CGLIBProxyFactory();
        TomsonYipin proxy = proxyFactory.getProxy();
        System.out.println(proxy.getClass());
        proxy.retend();
    }
}

 分析

1.声明目标对象

2.创建Enhancer对象(对标JDK动态代理中的Proxy类)

3.设置父类的字节码对象,指定父类;

4.设置回调函数(实现了MethodInterceptor接口的类,也就是当前类)

该接口类似于JDK动态代理中的InvocationHandler,因此实现的intercept方法其实就是我们调用代理对象中的方法时实际执行的方法;

5.调用enhancer中的create方法创建代理对象并返回

对比

动态代理和静态代理

动态代理与静态代理相比较,最大的好处是接口中声明的方法都被转移到调用处理器一个集中的方法处理(也就是InvocationHandler中的invoke方法),这样在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转;

如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度,而动态代理不会出现该问题;

JDK动态代理和CGLIB动态代理

使用CGLIB实现动态代理,CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高,唯一需要注意的是;CGLIB不能对声明为final的类或者方法进行代理,因为CGLIB原理是动态生成被代理类的子类;

在JDK1.6、1.7、1.8逐步对JDK动态代理优化后,在调用次数较少的情况下,JDK代理效率要高于CGLIB代理效率,只有当进行大量调用的时候,JDK1.6和1.7比CGLIB代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLIB代理,所以如果有接口则使用JDK动态代理,如果没有接口则使用CGLIB动态代理;

优缺点

优点

1.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

2.代理对象可以扩展目标对象的功能;

3.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

缺点​​​​​​​

增加了系统的复杂度;

使用场景

1.远程代理

本地服务通过网络请求远程服务,为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常,为良好的代码设计和可维护性

2.防火墙代理

当我们将浏览器配置成使用代理功能时,防火墙就将我们的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给我们的浏览器;

3.保护代理

控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限;

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

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

相关文章

婴儿摇篮语音播放芯片,高品质MP3音乐播放芯片,WT2003H

婴儿摇篮是一种用于帮助婴儿入睡的设备。传统的婴儿摇篮通常只是简单的摇晃&#xff0c;但是带有语音播报芯片的婴儿摇篮则可以更好地模拟妈妈的声音&#xff0c;从而更有效地帮助婴儿入睡。 如果您正在寻找高品质音乐摇篮方案&#xff0c;那么WT2003H语音播放芯片&#xff0c…

5月7日 2H55min|5月8日8H50min|时间轴复盘|14:00~14:30

5月8日 todo list list4 40min ✅ |实际上用了50+50 list6 40min ✅ |实际上用了30+60 阅读+听力连做 100min ✅ 口语 day01 ✅ 口语 day02 口语 day03

6、并发事务控制MVCC汇总

1.并发事务控制 单版本控制-锁 先来看锁&#xff0c;锁用独占的方式来保证在只有一个版本的情况下事务之间相互隔离&#xff0c;所以锁可以理解为单版本控制。 在 MySQL 事务中&#xff0c;锁的实现与隔离级别有关系&#xff0c;在 RR&#xff08;Repeatable Read&#xff0…

vCenter Server 8.0U1 OVF:在 Fusion 和 Workstation 中快速部署 vCSA

vCenter Server 8.0U1 OVF&#xff1a;在 Fusion 和 Workstation 中快速部署 vCSA vCenter Server 8.0U1 系列更新 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vcenter-8-ovf/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#x…

Win上通过Jconsole查看Java程序资源占用情况(教程总结,一篇就够了)

最近需要读取一个大文件&#xff0c;为了判断有没有读取到内存中&#xff0c;需要一个能查看jar包占用内存的工具&#xff0c;一顿面向百度后&#xff0c;发现了jdk自带的工具Jconsole&#xff0c;将教程分享给大家 一、介绍 JConsole 是一个内置 Java 性能分析器&#xff0c;…

手把手教你使用unisat 交易市场|BRC20|Unisat

开始前先熟悉下这张平台市场标注图&#xff0c;能让你跟得心应手&#xff01; 一、查看实时成交信息&#xff08;已 moon 为例子&#xff09; 搜索进入Token界面&#xff0c;点击 Orders 可以看到 1w 枚成交 87.86U&#xff08;单价 30 聪&#xff0c;大约 0.008786u&#xf…

牛客网剑指offer|中等题day2|JZ76删除链表中的重复节点、JZ23链表中环的入口节点、JZ24 反转链表(简单)

JZ76删除链表中的重复节点 链接&#xff1a;删除链表中重复的结点_牛客题霸_牛客网 参考代码&#xff1a; 自己好像还是偏向双指针这种想法&#xff0c;所以用了两个指针&#xff0c;这样感觉更好理解一些。 对了&#xff0c;去重有两种&#xff0c;我一开始写成了简单的那种&a…

MGV3001_ZG_当贝纯净桌面-线刷固件包

MGV3001_ZG_当贝纯净桌面-线刷固件包-内有教程及短接点 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&#xff0c;运…

【标准化方法】(3) Group Normalization 原理解析、代码复现,附Pytorch代码

今天和各位分享一下深度学习中常用的标准化方法&#xff0c;Group Normalization 数据分组归一化&#xff0c;向大家介绍一下数学原理&#xff0c;并用 Pytorch 复现。 Group Normalization 论文地址&#xff1a;https://arxiv.org/pdf/1803.08494.pdf 1. 原理介绍 在目标检测…

Javascript - Cookie的获取和保存应用

在之前的博客介绍了如何利用 Selenium去搭建 cookie池&#xff0c;进行自动化登录、获取信息等。那什么是cookie呢&#xff1f;它的作用又是什么呢&#xff1f; 这里&#xff0c;再重复简单介绍一下。 cookie 是浏览器储存在用户电脑上的一小段文本文件。该文件里存了加密后的用…

LeetCode之回溯算法

文章目录 思想&框架1.组合/子集和排列问题2.组合应用问题 组合/子集问题1. lc77 组合2. lc216 组合总和III3. lc39 组合总和4. lc40 组合总和II5. lc78 子集6. lc90 子集II 排列1. 全排列I2. 全排列II 组合问题的应用1.lc17 电话号码的字母组合2.lc131 分割回文串3. lc19 复…

集约式智能自动化办公,实在智能门户开启政企数字化转型新范式

导语&#xff1a; 随着数字化和智能化的快速发展&#xff0c;数字技术已经深入到各个行业和领域。实在智能基于数字员工在行业的深厚理解和丰富的实践经验&#xff0c;打造一站式的智能化统一平台——智能门户&#xff0c;打破了技术壁垒和系统数据之间的割裂感&#xff0c;实现…

软考A计划-重点考点-专题五(计算机网络知识)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Apache Sentry

官方 说明 Sentry是一种用于在Hadoop集群中控制和管理访问权限的工具。因此&#xff0c;CDH的Sentry指的是Cloudera Distribution for Hadoop中集成的Sentry组件&#xff0c;用于管理Hadoop集群中的访问控制和权限管理。 作用 Sentry是一个用于管理Hadoop集群中的访问权限的…

基于C++实现旅行线路设计

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 系统根据风险评估&#xff0c;为旅客设计一条符合旅行策略的旅行线路并输出&#xff0c;系统能查询当前时刻旅客所处的地点和状态&#xff08;停留城市/所在交通工具&#xff09;。 实验内容和实验环境描述 1.1 实验内容 城…

【吐槽贴】项目经理如何进行高效沟通?

“项目最大的风险就是都觉得没有风险。” 这还是跟同行聊天时开玩笑的一句话&#xff0c;最近我却深有体会。一直以为一切正常的项目&#xff0c;最近却接连出了问题&#xff0c;复盘才发现几个关键性问题都出在沟通方面&#xff0c;还一直认为沟通能力是自己的优势。这次主要踩…

使用java-timeseries库,使用arima算法预测时间序列(

项目地址&#xff1a; GitHub - signaflo/java-timeseries: Time series analysis in Java maven&#xff1a; <dependency><groupId>com.github.signaflo</groupId><artifactId>timeseries</artifactId><version>0.4</version> &…

【剖析STL】String

1.什么是STL&#xff1f; 标准模板库&#xff08;Standard Template Library&#xff0c;STL&#xff09;是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。虽说它主要出现到C中&#xff0c;但在被引…

Dockerfile部署java项目

一、dockerfile展示 将DockerFile 配置文件放到 maven项目目录内&#xff0c;和pom.xml同级。 # Download code FROM bitnami/git:2 AS git RUN mkdir -p /home/app/src RUN git -c http.sslVerifyfalse -C /home/app/src clone -b local https://github.com/test.git# # Bui…

2023年宜昌市中等职业学校技能大赛 “网络搭建与应用”竞赛题-1

2023年宜昌市中等职业学校技能大赛 “网络搭建与应用”竞赛题 一、竞赛内容分布 “网络搭建及应用”竞赛共分二个部分&#xff0c;其中&#xff1a; 第一部分&#xff1a;企业网络搭建部署项目&#xff0c;占总分的比例为50%&#xff1b; 第二部分&#xff1a;企业网络服…