美文网首页
源码角度来看代理Proxy类

源码角度来看代理Proxy类

作者: walker113 | 来源:发表于2020-05-26 09:24 被阅读0次
  • 近来在研究Retrofit的源码,发现使用了动态代理的方式;发现自己一直以来都是对这个方式一知半解,这次想要彻底的弄明白。

静态代理

要想了解动态代理,首先要知道静态代理,网上也有很多相关的文章,无非是说明相关的代码怎么写,等等,看完好像是明白的,但是到了自己使用的时候你会感觉到其实自己还是不怎么懂;

那下面开始说一下静态代理:

  • 先看一下静态代理的类图:


    image.png
  • 从类图我们可以看到 SubjectProxy(代理类)需要代理目标类(SubjectImp),然后调目标类中的方法;
    1. SubjectProxy(代理类)必须持有目标类(SubjectImp)的引用;
    2. 外层首先调SubjectProxy类的callMethod()方法,然后在callMethod()方法内调用目标类(SubjectImp)的callMethod()方法;
  • 因此不仅需要新建一个目标类对象,同时还需要新建一个代理类对象;

代码实例

嗯,看起来好像有点啰嗦,那就代码说话吧;

  • 假设现在有个学生接口,需要做作业和上英语课
public interface IStudent {
    void doHomework();
    void learnEnglish();
}
  • 我们来定义一个中学生类,那么中学的学生是怎么做的呢?
public class MiddleSchoolStudent implements IStudent {
    @Override
    public void doHomework() {
        System.out.println("做中学作业");
    }
    @Override
    public void learnEnglish() {
        System.out.println("上中学的英语课");
    }
}

现在有一个需求,需要计算做中学作业以及上英语课所花费的时间是多少?

  • 那么你可以选择直接在MiddleSchoolStudent 类种修改,但是可以能存在小学生类、大学生类,如果直接在类中修改,那么就需要修改很多地方,就破坏了闭合原则;
  • 那应该怎么做呢,可以使用使用一个时间计算的代理类,还记得上面类图吗,代理类需要实现需要代理的接口;
public class TimeProxy implements IStudent {
    private final IStudent student;
    public TimeProxy(IStudent student) {
        this.student = student;
    }
    @Override
    public void doHomework() {
        TimeUtil.start("doHomework");
        student.doHomework();
        TimeUtil.finish("doHomework");
    }
    @Override
    public void learnEnglish() {
        student.learnEnglish();
    }
}
  • 然后通过代理类来做计算:
public static void main(String[] args) {
    IStudent midStu = new MiddleSchoolStudent();
    TimeProxy timeProxy = new TimeProxy(midStu);
    timeProxy.doHomework();
}

动态代理

  • 现在静态代理讲清楚了,那下面来看看动态代理,让我们代码先行。

代码实例

使用的Java提供的Proxy类来创建动态代理;

  • 首先需要实现InvocationHandler类,为什么不叫InvocationProxy呢?难道这个类不是代理类吗?
static class Invocation implements InvocationHandler {
    private final IStudent student;

    public Invocation(IStudent student) {
        this.student = student;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        TimeUtil.start(method.getName());
        Object result = method.invoke(student, args);
        TimeUtil.finish(method.getName());
        return result;
    }
}
  • 然后我们在Main函数中调用方法Proxy来创建动态代理;
public static void main(String[] args) {
    IStudent midStu = new MiddleSchoolStudent();

    IStudent proxy = (IStudent) Proxy.newProxyInstance(
            IStudent.class.getClassLoader(),
            new Class[]{IStudent.class},
            new MidStudentProxy(midStu));
    proxy.doHomework();
}
  • 看到这里大家已经有很多疑惑,Proxy内部是怎么做到的呢?为什么要实现InvocationHandler类?

断点追踪

  • 下面让我们通过断点的方式来查询Proxy.newProxyInstance究竟做了什么工作?
  1. 首先我们点击进入newProxyInstance函数,在内部添加一个断点,然后使用debug模式运行;


    newProxyInstance.png
    image.png
  2. 跟踪下去,我们可以发现通过getProxyClass0() 方法,产生了一个$Proxy0类;


    Proxy0.png
  • 这个$Proxy0是什么东西呢?
  1. 可以看到,这里获取了$Proxy0类的构造器,并且传入的参数为InvocationHandler对象;
  • 在代码最后调用了newInstance实例化了一个$Proxy0对象,h为我们创建的Invocation类;


    image.png
  1. 让我们继续追踪,proxy.doHomework()方法的调用过程


    image.png
  • 可以发现 proxy 是系统通过Proxy.newProxyInstance() 方法 生成的一个新的对象Proxy0,这个Proxy0类实现了IStudent接口,包裹了一个我们传进去的Invocation对象,而Invocation对象又持有了一个目标类的引用;
  • 因此,真正的代理类并不是Invocation,而是$Proxy0这个系统生成的代理类;

$Proxy0 是如何产生的?

  • 那现在回到我们之前产生的问题,$Proxy0这个类是如何生成的呢?

    • 其实上面已经分析到了,是通过 getProxyClass0() 这个方法来生成的,下面我们一起来看看这个方法。
    • 查看源码发现Proxy类内部有一个ProxyClassFactory类。
      • ProxyClassFactory.png
    • 继续跟踪,忽略我们并不关心的功能,发现ProxyClassFactory内部有一个方法,看注释很明显就是生成代理类操作,ProxyGenerator.generateProxyClass();
      • generateProxyClass.png
  • 下面我们来验证一下,ProxyGenerator.generateProxyClass () 这个方法到底是不是如我们所猜想的,是通过它来生成代理类的呢?

  • 我们可以把按照这个方法来生成对应的代理类;
public static void main(String[] args) {
    IStudent midStu = new MiddleSchoolStudent();
    IStudent proxy = (IStudent) Proxy.newProxyInstance(
            IStudent.class.getClassLoader(),
            new Class[]{IStudent.class},
            new Invocation(midStu));

    proxy.doHomework();
    proxy.learnEnglish();

    byte[] bytes = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces());
    try {
        // 保存到proxy.class 文件中
        FileOutputStream fileOutputStream = new FileOutputStream(new File("out/proxy.class"));
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 打开proxy.class文件,让我们来见识一下通过ProxyGenerator.generateProxyClass()生成的代理类的庐山真面目:
    • proxy.class.png
    • 可以看到,这个代理类实现了我们定义的IStudent接口;

总结

  1. 也就是说,当我们调用Proxy.newProxyInstance的时候,它会根据传递的接口,来声明对应接口的代理类 $Proxy0 ;
  2. 我们在上面的分析已经知道,h 是创建$Proxy0 传递的Invocation类,可以看到在各个方法内部都调用了invoke方法;那如何调用invoke这就可以解释的通了;
  3. Proxy0 的doHomework() 方法,实际上调用的是Proxy0代理类对象的doHomework()方法,此方法内部持有Invocation对象,那实际是调用Invocation对象的invoke() 方法,然后再调用 Invocation内部目标类对象的learnEnglish() 方法;

相关文章

  • 源码角度来看代理Proxy类

    近来在研究Retrofit的源码,发现使用了动态代理的方式;发现自己一直以来都是对这个方式一知半解,这次想要彻底的...

  • 支持多线程的代理IP池

    python源码 使用示例: pool = Proxy() 构造代理池类 pool = Proxy(min_poo...

  • Proxy代理类型

    四类代理 透明代理(Transparent Proxy) 匿名代理(Anonymous Proxy) 混淆代理(D...

  • Java-反射-动态代理

    概述 Proxy 提供静态方法来创建动态代理类和动态代理对象; 源码基于 Android-SDK-29 中的 JD...

  • JAVA动态代理的实现原理

    java动态代理主要是有Proxy和InvocationHandler两个类实现的一、我先来看下如何使用Proxy...

  • java随笔(十一)

    java动态代理源码分析,总结。java动态代理实现步骤: 通过阅读源码发现,动态生成代理对象$Proxy0,该对...

  • Java23种设计模式之结构型模式「代理模式」

    代理模式 - Proxy Pattern 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这...

  • zabbixApi4j-Proxy

    Proxy:这个类被设计成与代理一起工作。 proxy.create: 创建新的代理proxy.delete: 删...

  • 4

    6、java动态代理:两个重要类: 1、Proxy:jdk提供的帮助类(类似于工厂)用于生成代理实例,Proxy....

  • GCLIB动态代理

    1,简介 1)对比JDK动态代理,由于JDK动态代理生成的proxy对象,都是继承了Proxy类,并实现了所代理的...

网友评论

      本文标题:源码角度来看代理Proxy类

      本文链接:https://www.haomeiwen.com/subject/hkltahtx.html