美文网首页
源码角度来看代理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类

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