Redian新闻
>
一文搞懂 Java 动态代理,so easy!

一文搞懂 Java 动态代理,so easy!

公众号新闻

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入芋道快速开发平台知识星球。下面是星球提供的部分资料: 

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn

来源:www.jianshu.com/p
/b3e67ba70b51


前言

本来是打算把java代理模式给写一下的,但是整理思路的时候发现这是一个庞大的工程,我需要讲清楚什么是代理模式;它的应用场景有哪些;代理又分为静态代理和动态代理,它们分别是如何实现的,区别又是什么,我还要举例,分析源码,emm。

显然,我现在的时间安排是无法完成这个庞大的工程的,所以我就讲一下目前解决问题中遇到的动态代理吧(默认你大致了解代理模式)

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

为什么要写这篇文章

我最近在学习Retrofit2源码,而这个框架比较核心的一点就是动态代理,所以在这里把我学习过程中的一些我认为比较关键的地方整理出来,分享给有需要的童鞋。

Retrofit2的动态代理到底体现在哪里?请看下面代码

//retrofit的API接口对象
ApiService apiService;
//创建代理对象
 apiService = retrofit.create(ApiService.class);
//调用代理类中的方法
apiService.xxx();

这是retrofit.create方法的源码,很明显的动态代理

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

动态代理的本质是什么?

我的理解:

  • 提供一个代理来控制对象的访问;
  • 程序运行时动态生成代理类,这个代理类继承于Proxy,并且实现自定义的委托类的接口;
  • 丰富原始类的操作

动态代理的具体实现

本来是不想写一堆代码来说动态代理的代码是如何实现的,但是没办法,有些问题不通过举例无法说清楚,下面一起来看一个简单的例子吧:

学生每学期都需要参加期末考试的

public interface Student {
    //参加考试
    void exam();
}

学生分为很多专业的,其中计算机专业的学生需要参加计算机专业考试

public class ComputerStudent implements Student {
    private String name;

    public ComputerStudent(String name) {
        this.name = name;
    }

    @Override
    public void exam() {
        System.out.println(name + " 参加计算机专业考试");
    }
}

由于每个学生的专业能力不一致,有的需要在考试前刷刷题,有的需要去找学霸辅导一下(抱大腿),等等,总之每个学生在考试前都需要做一些事情。

如果现在需要定义很多不同特点的学生,你怎么做呢?如果去一个个定义不同的行为的话,那将是非常庞大的工作量,那有没有简单的办法呢?答案是有的,通过代理类就可以实现。

事实上我们没有必要去定义每个学生,因为每个学生的行为是没办法确定的,我们可以通过动态代理在它做这个动作的时候去实现他的特定行为。

public class Test {
    public static void main(String[] args){
        //生成$Proxy0的class文件
        //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        //被代理类
        final Student jack =new ComputerStudent("jack");

        //生成代理对象
     Student jackProxy= (Student) Proxy.newProxyInstance(jack.getClass().getClassLoader(),
                jack.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("before exam do something");

                        //通过反射调用对象的方法
                        method.invoke(jack,args);

                        System.out.println("after exam do something");

                        return null;
                    }
                });

        //代理方法调用
        jackProxy.exam();
    }
}

当调用代理类的exam()方法,程序运行结果如下

这里有个重点,通过代理类对象jackProxy去调用方法和接口实现类对象jack去调用方法是有明显区别的,通过代理方式去调用,可以在原来方法执行前后做一些其它操作,这就是代理模式的特点。

那些你容易忽略的细节

首先回顾一下动态代理的实现流程:

  • 1、通过Proxy.newProxyInstance方法创建一个代理对象;
  • 2、在内部类InvocationHandlerinvoke()方法中做一些操作:利用反射调用被代理类(这里是ComputerStudent)中的方法,通常在这个调用方法前后还会做一些其它操作;
  • 3、代理对象jackProxy调用方法 对整个流程有个了解之后,下面来看一些细节问题。

Q1:Proxy.newProxyInstance中的3个参数到底是什么?为什么要穿入这3个参数?

系统接口定义如下:

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

  • loader :定义代理类的类加载器,这里要代理的是jack,所以用jack的类加载器
  • interfaces :是一个接口类的集合,具体来说是代理类实现的接口的集合,也是被代理类实现的接口的集合;
  • h :代理类对象调用方法时需要用到的一个接口对象,在系统生成的代理类内部会用到它。

到这里,我想细心的童鞋会想这个代理类到底是什么?似乎从头到尾没有露面过。的确是这样,即使你去翻遍源码你也找不到这个代理类,因为在动态代理模式中它是在运行时生成的,所以你在源码甚至.class中都找不到他的影子。

我先说结论:运行时动态生成的代理类叫做$Proxy0,关于这个类怎么看源码,很多介绍代理的文章都没有说清楚,读者也是一脸懵逼;如果你想要看它的内容,可以通过如下方法

public class Test {
    public static void main(String[] args){
        //生成$Proxy0的class文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");

        //被代理类
     Student jack =new ComputerStudent("jack");
        //生成代理对象
     Student jackProxy= (Student) Proxy.newProxyInstance();
}

在运行方法中添加这一行代码,在运行后会自动在项目根目录生成com文件夹,其中...\IdeaProjects\com\sun\proxy下会生成$Proxy0.class文件。注意:我是在IDEA上调试成功的,我在Android Studio上测试是没有生成的。暂时不知道原理,有了解的大佬可以科普一下。System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

代理类$Proxy0源码如下:

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import student.Student;

public final class $Proxy0 extends Proxy implements Student {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void exam() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("student.Student").getMethod("exam");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

关于$Proxy0先说几个结论:

  • $Proxy0 继承了Proxy,实现了自定义的目标接口Student;
  • $Proxy0定义了接口Student中的方法exam(),以及Object对象的几个方法equals()、toString()、hashCode()
  • 构造方法中传了InvocationHandler对象,并且在定义的方法中调用了它的invoke()方法

回到刚才的问题,Proxy.newProxyInstance为什么要穿入这3个参数?

        //生成代理对象
     Student jackProxy= (Student) Proxy.newProxyInstance(jack.getClass().getClassLoader(),
                jack.getClass().getInterfaces(),
                new InvocationHandler() );

因为代理对象是基于自定义接口Student和jack类加载器代理出来的。

Q2:InvocationHandler的作用及其invoke()方法等解释

当代理对象调用方法时,会回调执行刚才new出来的InvocationHandler中的invoke()方法。

关于invoke()方法,在看了源码和反复代码验证之后,我做出的解释如下:

 /**
     * 这个方法不是我们显示的去调用,是系统生成的代理类$Proxy0中调用的
     *
     * @param proxy  代理对象:也就是Proxy.newProxyInstance返回的对象
     * @param method 要调用的方法
     * @param args   调用方法的参数
     * @return
     * @throws Throwable
     */

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}

注意: invoke()不是显示调用的,是在代理类中去调用的。比如调用exam()方法时, 该方法中会调用super.h.invoke(this, m3, null);,就是调用父类的h的invoke(),它的父类是Proxy,h是一个InvocationHandler对象;所以说当调用exam()方法时最后回调到刚才new出来的InvocationHandler的invoke方法。

public final void exam() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

下面再来看一个问题

public class Test {
    public static void main(String[] args){
        //生成$Proxy0的class文件
        //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        //被代理类
        final Student jack =new ComputerStudent("jack");

        //生成代理对象
     Student jackProxy= (Student) Proxy.newProxyInstance(jack.getClass().getClassLoader(),
                jack.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("before exam do something");

                        //通过反射调用对象的方法
                        method.invoke(jack,args);

                        System.out.println("after exam do something");

                        return null;
                    }
                });

        //方法调用
        jackProxy.exam();
    }
}

看一下InvocationHandler中invoke()方法内部的调用

  //通过反射调用对象的方法
  method.invoke(jack,args);

这句代码的作用是通过反射调用一个方法,如果把实现类对象jack换成代理类对象proxy会发生什么?

结论:会循环报错,停不下来那种。因为proxy是代理实例,也就是这里的jackProxy,当这个对象的方法被调用的时候会触发InvocationHandler中invoke()方法,而InvocationHandler内部又再次调用proxy的方法,如此不停循环。

动态代理的使用场景

优点:在运行时切入原始类,改变类的方法,这样可以丰富该方法的操作,比如在方法之前、之后做一些其它操作。

应用的话,比如Retrofit框架、AOP(面向切面编程)等等。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
一文搞定专属码的设计与开发限时领丨一文搞懂音节划分,自然拼读才能如虎添翼!附音节规则海报!绝对干货!Synopsys官宣入局RISC-V:发布新处理器,加入RISC-V国际董事会𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢三防夹克,经典、优雅不凡的英伦风!还送长袖T恤!火了172年!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢来了,又好穿又有品!!一文搞定Nginx的压缩、黑白名单、防盗链、零拷贝、跨域、双机热备等知识全英万圣节攻略!吃喝玩乐、战袍购买、南瓜灯制作... 一文搞定!换季大捡漏!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢短袖买一送一!库存不多,手慢无!“王侯将相,宁有种乎?”的换言之:进了澡堂,都一样。——— 翻读清徐文靖《管城硕记》英国百年贵族奢牌𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢休闲卫衣!经典、优雅不凡的英伦风~好东西真不一样!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢雅格狮丹卫裤,1折限量抢!真没想到!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢(雅格狮丹)风衣都让我们找来了(1折限时抢)户外是大趋势!Nike中国最大代理商新增Hoka和凯乐石代理 安踏也表示同意移民生活(22)一个家庭悲剧的酿成真没想到风衣1折!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢(雅格狮丹)风衣都让我们找来了lulu折扣区更新了,大量scuba(绿金、Java)define打折,Blissfeel 运动鞋黑色才79换季大捡漏!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢经典纯色T恤,买一送一!一文搞懂朝鲜军人胸前的集体荣誉证章从《围城》想到野鸡大学真没想到!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢雅格狮丹的风衣都让我们找来了,1折限时抢数学难?借用披萨学习《几何中的角》,So easy!【 独家代理】又一套由千万经纪赵妍 Cindy Zhao 代理的别墅遭到疯抢!1折入!穿过国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢的人,才是真正的有品!2023•秋•全球招聘季|简历脱颖而出,来看招聘总监一文搞定简历修改!1折入!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢雅格狮丹三防夹克,下单送长袖T恤!一文搞定 Nginx 压缩、黑白名单、防盗链、零拷贝、跨域、双机热备等知识1折入!英国重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢,专柜同款Polo衫,舒适、透气、高品质!1折入!穿过国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢的男人,才会明白什么是品质!购物季,提醒大家关于一些所谓的出海品牌,EASY RETURN ISN’T THAT EASYScuba Oversized Java: 全拉链带帽 vs 半拉链立领Easy like Sunday morning...全奖Psy.D./带奖Psy.D.系列学校介绍汇总第九章 政府公权力的组织运作 (4)Cuyana Easy Tote 我太喜欢了!!!!!1折入!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢(雅格狮丹)三防夹克,防风防污防泼水,好穿又有品!火了172年!国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢长袖Polo衫,又好穿又有品!对谈五大代理商:行业变革期,代理商如何突围?国际重奢𝘼𝙦𝙪𝙖𝙨𝙘𝙪𝙩𝙪𝙢中长款风衣,品位与优雅,兼顾帅气和女人味!一文搞懂 GPU 的概念、工作原理,以及与 CPU 的区别5135 血壮山河之武汉会战 信罗战役 8
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。