源码角度来看代理Proxy类

  • 近来在研究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类实现了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()方法,此方法内部持有Invocation对象,那实际是调用Invocation对象的invoke() 方法,然后再调用 Invocation内部目标类对象的learnEnglish() 方法;

你可能感兴趣的:(源码角度来看代理Proxy类)