【设计模式】代理模式

news2024/11/24 21:00:10

代理模式

为某个对象提供一种代理,以控制其他对象对这个对象的访问。属于结构型模式。

某些情况下,一个对象A不适合或者不能引用、直接访问某个对象B,而代理对象可以在客户端A和目标对象B之间起到中介作用
在这里插入图片描述
代理模式主要有三个重要角色:

  1. 抽象角色(subject): 声明真实subject和代理subject共同的接口方法
  2. Real Subject : 被代理的真实对象
  3. Proxy: 代理对象, 持有real subject的引用

静态代理

代理对象持有真实对象的引用

public class Proxy implements ISubject{

    private RealSubject realSubject;


    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    public void before() {
        System.out.println("before real subject call");
    }

    @Override
    public void request() {
        before();
        realSubject.request();
        after();
    }

    public void after() {
        System.out.println("after real subject call");
    }
}

动态代理

JDK动态代理

public interface IPerson {

    void rentRoom();
}
public class RoomProxy implements InvocationHandler {

    private IPerson person;

    public IPerson getInstance(IPerson person) {
        this.person = person;
        Class<?> clazz = person.getClass();
        return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    public void before() {
        System.out.println("收到房子需求,物色房源....");
    }

    public void after() {
        System.out.println("签合同,成交.....");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.person, args);
        after();
        return result;
    }
}

JDK动态代理实现

JDK动态代理采用字节重组,重新生成对象来代替原始对象,以达到动态代理的目的
jdk动态代理生成对象的步骤如下:

1.获取被代理对象的引用,通过反射获取被代理对象的所有接口
2.JDK动态代理类重新生成一个新的类A,A要实现被代理类实现的所有接口。
3.动态生成Java代码
4.将新生成的Java代码编译为class文件
5.将编译好的代理类的class文件加载到JVM中运行

通过如下代码得到一个代理类的class文件,然后通过jad反编译得到$Proxy.jad

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{IPerson.class});
        FileOutputStream fos = new FileOutputStream("D://$Proxy.class");
        fos.write(bytes);
        fos.flush();
        fos.close();

可以看到新生成的 $Proxy 继承了Proxy类,并且实现了IPerson接口,重写了接口的rentRoom方法(这里的invocationHandler就是我们定义的RoomProxy实例)。
还在静态代码块中通过反射获取了被代理对象的所有方法,并保存了对应方法的引用,用于在重写的方法中通过反射调用被代理对象的方法

注意下: 从下述的源码中可以看到,所有重写后的方法都是通过h.invoke调用的,也就是都会执行我们定义在RoomProxy中的前置和后置逻辑

 
public final class $Proxy extends Proxy
    implements IPerson
{

    public $Proxy(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

    public final boolean equals(Object obj)
    {
        try
        {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void rentRoom()
    {
        try
        {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString()
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode()
    {
        try
        {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m3 = Class.forName("com.cpp.proxy.jdk.IPerson").getMethod("rentRoom", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

CGLib动态代理

和JDK动态代理不同,CGLib动态代理的目标对象不需要实现任何接口,它是通过动态继承目标对象来实现动态代理的

public class Renter {

    public void rentRoom() {
        System.out.println("我要租一个房子");
    }

}
public class RoomProxy implements MethodInterceptor {


    public Object getInstance(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    private void before() {
        System.out.println("收到请求,帮你找房子");
    }

    private void after() {
        System.out.println("签订合同,交中介费");
    }
}

CGLib动态代理实现原理

  public static void main(String[] args) {
        //将内存中的class类写到磁盘,然后通过jad反编译
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D://cglib_proxy//");
        Renter renter = (Renter) new RoomProxy().getInstance(Renter.class);
        renter.rentRoom();
    }

可以看到生成了三个class文件
在这里插入图片描述
通过反编译的文件可以看出Renter$$EnhancerByCGLIB$$56be5228是生成的代理类
1.生成的代理类继承了Renter类
2.代理类持有MethodInterceptor 对象
3.动态代理类会重写父类 Renter的非 final、private 方法;也会构建自己的cglib 方法,对应方法名为CGLIB+$父类方法名$
4.对于自己的cglib 方法:通过super.方法名,直接调用父类;对于重写的方法:它会调用拦截器中的 intercept() 方法
5.methodProxy.invokeSuper() 方法会调用动态代理类中的 cglib 方法;methodProxy.invoke() 方法会调用动态代理类中的重写方法,所以如果我们在RoomProxy 拦截器里调用methodProxy.invoke() 就会出现死循环

public class Renter$$EnhancerByCGLIB$$56be5228 extends Renter
    implements Factory
{
    private MethodInterceptor CGLIB$CALLBACK_0;
    static void CGLIB$STATICHOOK1()
    {
        CGLIB$rentRoom$0$Proxy = MethodProxy.create(classloader, (CGLIB$rentRoom$0$Method = Class.forName("com.cpp.proxy.cglib.Renter").getDeclaredMethod("rentRoom", new Class[0])).getDeclaringClass(), class1, "()V", "rentRoom", "CGLIB$rentRoom$0");
        //...
        return;
    }
    //methodProxy.invokeSuper() 方法会调用
    final void CGLIB$rentRoom$0()
    {
        super.rentRoom();
    }
    //methodProxy.invoke()方法会调用 
    public final void rentRoom()
    {
        //...
        this;
        CGLIB$rentRoom$0$Method;
        CGLIB$emptyArgs;
        CGLIB$rentRoom$0$Proxy;
        //调用拦截器的intercept()方法
        intercept();
        return;
        super.rentRoom();
        return;
    }
    //...
}

整个调用路径为:
代理对象调用rentRoom() -> 调用重写的rentRoom()方法 -> 调用拦截器的intercept()方法 -> 调用methodProxy.invokeSuper() -> 调用CGLIB$rentRoom$0()-> 调用父类(被代理对象)的rentRoom()方法

那么其他两个class分别是什么呢?
CGLib共生成3个类,分别为代理类,代理类的FastClass,目标类的FastClass,它采用了FastClass的机制来实现对被拦截方法的调用。

FastClass机制就是对一个类的方法建立索引,然后通过索引来直接调用相应的方法, 而不需要通过反射来调用,其调用效率比JDK动态代理通过反射调用效率高

FastClass的初始化是通过init()方法实现的,初始化时机为调用methodProxy.invokeSuper()methodProxy.invoke()方法,在methodProxy.invokeSuper()调用的时候会生成FastClassInfo(第一次生成后会放入到缓存中)


    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1, desc);
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new CreateInfo(c1, c2);
        return proxy;
    }
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            //初始化FastClassInfo
            this.init();
            FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }
    
    private void init() {
        if (this.fastClassInfo == null) {
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                    CreateInfo ci = this.createInfo;
                    FastClassInfo fci = new FastClassInfo();
                    //ci.c1=目标类的class fci.f1=目标类的FastClass(从缓存获取,如果没有则生成新的FastClass并放入缓存中)
                    fci.f1 = helper(ci, ci.c1);
                    //ci.c2=CGLib生成的代理类的class fci.f2=代理类的FastClass
                    fci.f2 = helper(ci, ci.c2);
                    //方法在目标类的FastClass中的索引(这里只是一个接口,具体的实现在CGLib里生成的子类里)
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    //方法在代理类的FastClass中的索引 
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                    this.createInfo = null;
                }
            }
        }

    }

看下getIndex()的具体实现

 public int getIndex(Signature signature)
    {
        String s = signature.toString();
        s;
        s.hashCode();
        JVM INSTR lookupswitch 10: default 204
           goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L8 _L9 _L10 _L11
_L2:
        "getClass()Ljava/lang/Class;";
        equals();
        JVM INSTR ifeq 205;
           goto _L12 _L13
_L13:
        break MISSING_BLOCK_LABEL_205;
_L12:
        return 7;
        //...
        "rentRoom()V";
        equals();
        JVM INSTR ifeq 205;
           goto _L18 _L19
_L19:
        break MISSING_BLOCK_LABEL_205;
_L18:
        return 0;
        //...
        JVM INSTR pop ;
        return -1;
    }
    //通过index直接定位进行方法调用
 public Object invoke(int i, Object obj, Object aobj[])
        throws InvocationTargetException
    {
        (Renter)obj;
        i;
        JVM INSTR tableswitch 0 9: default 152
           goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L8 _L9 _L10 _L11
_L2:
        rentRoom();
        return null;
_L3:
        ((Number)aobj[0]).longValue();
        ((Number)aobj[1]).intValue();
        //...
        JVM INSTR new #76  <Class InvocationTargetException>;
        JVM INSTR dup_x1 ;
        JVM INSTR swap ;
        InvocationTargetException();
        throw ;
_L1:
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

我们可以模仿FastClass的机制写一个demo,通过索引来进行方法调用

public class Renter {

    public void rentRoom() {
        System.out.println("我要租一个房子");
    }

    public String toString() {
      return "Renter String";
    }
}

public class RenterFastClass {
    public int getIndex(String s, Class[] classes) {
        int hashCode = s.hashCode();
        switch (hashCode) {
            case 1963324341:
                return 1;
            case 1774939245:
                return 2;
        }
        return -1;
    }

    public Object invoke(int i, Object o, Object[] objects) {
        Renter renter = (Renter) o;
        switch (i) {
            case 1:
                renter.rentRoom();
                return  null;
            case 2:
                return  renter.toString();
        }
        return null;
    }
}

    public static void main(String[] args) {
        RenterFastClass fs = new RenterFastClass();
        Renter renter = new Renter();
        int index = fs.getIndex("rentRoom()",new Class[]{Renter.class});
        fs.invoke(index, renter, null);
        int index2 = fs.getIndex("toString()",new Class[]{Renter.class});
        System.out.println(fs.invoke(index2, renter, null));

    }

执行结果如下:
在这里插入图片描述

总结:

  • CGLib动态代理继承了被代理的类;JDK动态代理实现了被代理类的接口
  • JDK动态代理和CGLib动态代理都是在运行时生成字节码文件,JDK动态代理直接写Class字节码,CGLib是用过ASM框架写字节码,CGLib生成代理类字节码文件的效率更低
  • CGLib使用了FastClass机制来调用方法,比通过反射调用的JDK动态代理的执行效率更高

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

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

相关文章

【推拉框-手风琴】vue3实现手风琴效果的组件

简言 在工作时有时会用到竖形手风琴效果的组件。 在此记录下实现代码和实现思路。 手风琴实现 结构搭建 搭建结构主要实现盒子间的排列效果。 用flex布局或者其他布局方式将内容在一行排列把每一项的内容和项头用盒子包裹&#xff0c; 内容就是这一项要展示的内容&#xf…

python16行代码获取原神全角色+全语音

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 本来是不玩原神的&#xff0c;但是实在是经不住双重诱惑呀~ 毕竟谁能拒绝角色风景超级好看又可以爬树、炸鱼、壶里造房子、抓小动物、躲猫猫的游戏捏~ 今天点进官网~角色得配音让我沉陷其中&#xff0c;于是 我决定把他们爬…

数据库可视化开发工具内容介绍

在现代化办公环境中&#xff0c;数据管理的重要性不言而喻。对于企业来说&#xff0c;将企业内部的数据做好规划和管理&#xff0c;可以给企业提升办公协作效率&#xff0c;为企业高层做出正确的经营决策奠定基础。本文主要给大家介绍的是数据化可视化开发工具的内容&#xff0…

狂神Springmvc,404,500错误解决办法(灵)

b站狂神springmvc404&#xff0c;500解决办法 首先校验各个文件是否正确 配置web.xml <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi"http://www.w3.org/2001/XMLSche…

HLS协议有哪些特别优势

阿酷TONY / 2023-3-3 / 长沙 可以实现码率的动态自适应&#xff0c;清晰度动态成为可能&#xff1b;HLS是基于HTTP 协议的&#xff0c;更易于做各平台的适配与兼容&#xff1b;多终端跨平台的支持性&#xff1a; PC端, Android端, IOS 平台&#xff0c;微信之类的都支持&am…

C++中邻接矩阵、邻接表、链式前向星具体用法及讲解

图论在提高组中几乎占据半壁江山&#xff0c;而今天要讲的就是如何存储一个图一.邻接矩阵原理要建立一个图&#xff0c;根本的要素就是边和点而想要让计算机存储边和点就需要用到一些数据结构邻接矩阵是最简单的他使用了一个二维数组&#xff0c;来表示一个图假设数组名为map那…

彻底搞清楚内存泄漏的原因,如何避免内存泄漏,如何定位内存泄漏

作为C/C开发人员&#xff0c;内存泄漏是最容易遇到的问题之一&#xff0c;这是由C/C语言的特性引起的。C/C语言与其他语言不同&#xff0c;需要开发者去申请和释放内存&#xff0c;即需要开发者去管理内存&#xff0c;如果内存使用不当&#xff0c;就容易造成段错误(segment fa…

Spark Streaming DStream转换

DStream上的操作与RDD的类似&#xff0c;分为Transformations&#xff08;转换&#xff09;和Output Operations&#xff08;输出&#xff09;两种&#xff0c;此外转换操作中还有一些比较特殊的算子&#xff0c;如&#xff1a;updateStateByKey()、transform()以及各种Window相…

打造优秀项目团队的3个核心原则

优秀的项目团队必须是高绩效的&#xff0c;而打造这样优秀团队需要3个核心原则&#xff1a;共同的目标、专业的技能和高效的协作。 1、共同的项目目标 项目团队的共同目标就是实现项目的交付成果。项目经理以远景宏大的方式将目标传递给团队成员&#xff0c;以激发团队成员的战…

jeesite多环境配置

jeesite多环境配置 参考网址&#xff1a; https://blog.csdn.net/shaoming314/article/details/129115912?spm1001.2014.3001.5501 开源项目地址&#xff1a; https://gitee.com/thinkgem/jeesite Spring Spring MVC mybatis Ehcache shiro mysql jsp (主要技术栈) 项目…

【大数据离线开发】8.3 Hive的数据模型

8.4 Hive的数据模型 Hive的数据存储 基于HDFS没有专门的数据存储格式存储结构主要包括&#xff1a;数据库、文件、表、视图可以直接加载文本文件&#xff08;.txt文件&#xff09;创建表时&#xff0c;指定Hive数据的列分隔符与行分隔符 8.4.1 内部表 hive 的内部表类似 My…

hexo静态网站部署到腾讯云cos

hexo支持很多部署方案&#xff0c;最直接的就是部署在GitHub Pages服务上&#xff0c;国内gitee、coding等代码托管平台也都支持静态网站服务&#xff0c;而且免费。 但是GitHub在国内访问不太稳定&#xff0c;国内的代码托管平台资源和服务也不太稳定&#xff0c;后来想了想&…

windows安装tomcat

这里写自定义目录标题tomcat官网下载安装包并解压环境变量配置启动tomcat访问http://localhost:8080/修复启动出现乱码问题tomcat官网下载安装包并解压 环境变量配置 系统环境变量新增&#xff1a; 变量名&#xff1a;CATALINA_HOME 变量值&#xff1a;tomcat的安装目录 编辑…

使用MAT进行内存分析,并找到OOM问题

前言 在处理一次现场问题时&#xff0c;发现服务还在运行&#xff0c;但是出现假死情况&#xff0c;后通过分析GC日志以及使用MAT分析确定问题是内存溢出OutOfMemery(OOM)&#xff1b;这里只记录MAT分析学习过程,最近工作忙&#xff0c;补记录。 GC日志分析 首先&#xff0c;如…

EM@三角函数诱导公式

文章目录诱导公式单位圆坐标和三角函数记忆口诀符号看象限奇变偶不变例常用诱导公式&#x1f388;常用部分(5对)倒数关系六种三角函数间的转换关系小结ReflectionsShifts and periodicity诱导公式 诱导公式 - 维基百科&#xff0c;自由的百科全书 (wikipedia.org) 单位圆坐标…

推送投票制作微信推送里投票制作教程在线投票活动制作

近些年来&#xff0c;第三方的微信投票制作平台如雨后春笋般络绎不绝。随着手机的互联网的发展及微信开放平台各项基于手机能力的开放&#xff0c;更多人选择微信投票小程序平台&#xff0c;因为它有非常大的优势。1.它比起微信公众号自带的投票系统、传统的H5投票系统有可以图…

一文看懂网上下单的手机流量卡为什么归属都是随机的!

最近很多网上下单的小伙伴们心中似乎都有一个疑问。那就是网上很多手机卡、流量卡都不能自选号码和归属地&#xff0c;就算能自选号码&#xff0c;归属地也是随机的而且很多都不会跟你说具体的城市&#xff0c;这是为什么呢&#xff1f;莫非其中有什么不可告人的秘密吗?小伙伴…

JetBrains IntelliJ支持自动切换输入法,写代码如丝般顺滑

背景简介对于母语为中文的开发者&#xff0c;写代码过程中经常需要在中/英输入法之间进行切换&#xff0c;而且由于不清楚当前处于哪种输入状态&#xff0c;有时输入到一半发现输入法错了&#xff0c;删除重新输入&#xff0c;有时切换了好几次都没有成功&#xff0c;实在太影响…

【强化学习】强化学习数学基础:蒙特卡洛方法

强化学习数学方法&#xff1a;蒙特卡洛方法举个例子举个例子1&#xff1a;投掷硬币The simplest MC-based RL algorithm举个例子2&#xff1a;Episode lengthUse data more efficientlyMC without exploring starts总结内容来源将value iteration和policy iteration方法称为mod…

无线耳机哪个品牌好一点?2023四款好用的无线耳机排行

随着蓝牙耳机的普及&#xff0c;越来越多的耳机厂商加入蓝牙耳机这条竞争赛道。不同品牌的蓝牙耳机又有着不同的价位区间&#xff0c;不同的性能配置&#xff0c;不同的外观设计&#xff0c;可以说现在的蓝牙耳机多到让人在选择时眼花缭乱。那么&#xff0c;无线耳机哪个品牌好…