美文网首页
JDK动态代理实践与原理

JDK动态代理实践与原理

作者: 蓝汝丶琪 | 来源:发表于2018-12-26 14:10 被阅读34次

    本篇内容

    1. 介绍JDK动态代理的基本概念
    2. 一些JDK动态代理的疑问
    3. JDK动态代理的Demo
    4. JDK动态代理的原理
    5. 心得

    主要思路

    • 被代理类:实际代码类,必须实现至少一个接口 (下文中的Man
    • 代理类:实现代理逻辑的代理类,继承Proxy类和实现被代理类同一个接口 (由JDK动态生成)
    • 接口:连接代理类和被代理类的桥梁 (下文中的Person接口

    为什么JDK动态代理必须要有接口呢?

    因为生成的代理类是实现了InvocationHandler()接口,代理的逻辑都是由这个接口的invoke()方法实现。因此这个实现接口的对象就是由Proxy来存储,所有代理类就必须要继承Proxy,因此要桥接代理类和被代理类就只能用接口了

    而为什么一定要继承Proxy
    应该是想

    • 制约必须是代理接口
    • 如果是允许继承类,那么字段的继承会浪费很大的内存,而且会存在final无法被重写的风险

    Demo

    
    //Person接口
    public interface Person {
    
        public void name();
    
        public void age();
    }
    
    --------------------------------------------------------------------------
    //被代理类    实现了Person接口
    public class Man implements Person {
    
        public void name() {
            System.out.println("我的名字是小米");
        }
    
        public void age() {
            System.out.println("我今年23");
        }
    
        public void sing() {
            System.out.println("我不会唱歌");
        }
    }
    
    --------------------------------------------------------------------------
    public class JDK {
    
        //获取代理类,只是创建了个类来实现重写InvocationHandler.invoke()方法
        static class ManProxy{
            private Man man;
    
            public ManProxy(Man man) {
                this.man = man;
            }
            
            //创建代理类过程 调用Proxy.newProxyInstance()
            public Person getProxy() {
                return (Person) Proxy.newProxyInstance(man.getClass().getClassLoader(), man.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public String toString() {
                        return "这是InvocationHandler的toString";
                    }
    
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (method.getName().equals("name")) {
                            System.out.println("你是在问我的名字吗?");
                            method.invoke(man, args);
                        } else if (method.getName().equals("age")) {
                            System.out.println("你是在问我的岁数吗?");
                            method.invoke(man, args);
                        } else if (method.getName().equals("sing")) {
                            System.out.println("你是在问我会唱歌吗?");
                            method.invoke(man, args);
                        }else if (method.getName().equals("toString")) {
                            System.out.println("你执行的是toString");
                            System.out.println(toString());
                            method.invoke(man, args);
                        }
    
                        return null;
                    }
                });
            }
        }
    
        public static void main(String[] args) {
            ManProxy manProxy = new ManProxy(new Man());
            Person proxy = manProxy.getProxy();
            proxy.name();
            proxy.age();
            System.out.println(proxy.toString());
    
        }
    
    }
    
    输出:
    你是在问我的名字吗?
    我的名字是小米
    你是在问我的岁数吗?
    我今年23
    你执行的是toString
    这是InvocationHandler的toString
    null    //这个输出是main方法里面的调用代理类Proxy0中的toString()方法
    
    
    

    核心方法

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

    过程

    JDK动态代理原理图.png
    • 首先需要代理的类实现InvocationHandler()接口,并重写invoke()方法
    • 代理类生成从Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)开始
      • ClassLoader类加载器:保证与被代理类是同一个类加载器
      • 被代理类的接口数组
    • 调用getProxyClass0()方法
      • 这里首先会从缓存中查找代理类
      • 如果缓存中没有,就会去创建代理类
    • 创建代理类是通过内部类ProxyClassFactory创建的
      • 通过apply()方法创建类
      • 首先是生成代理类的字节码
      • 然后通过字节码来生成一个类
    • 通过代理类的构造方法来创建一个实例,而且初始化的参数是我们实现了InvocationHandler()接口的对象

    动态生成的代理类Proxy0.class 反编译后是这样的

    
    // 省略了部分代码
    public final class $Proxy0 extends Proxy
      implements person
    {
    // person接口的方法
      private static Method m1;
      private static Method m2;
    
      public $Proxy0(InvocationHandler paramInvocationHandler)
        throws 
      {
        super(paramInvocationHandler);
      }
      
    // 这个代理类也重写了person的方法,我们调用m1方法的入口就只此处
      public final void m1(String paramString){
      // 全部都会调用到InvocationHandler方法中的invoke中。
      // invoke方法就是实现我们代理逻辑的方法
         this.h.invoke(this, m1, new Object[] { paramString });
      }
    
     public final void m2(String paramString){
      // 全部都会调用到InvocationHandler方法中的invoke中。
      // invoke方法就是实现我们代理逻辑的方法
         this.h.invoke(this, m1, new Object[] { paramString });
      }
    
    }
    
    

    心得

    为什么是动态

    • 因为是根据被代理类的接口和类加载器生成的字节码,然后在运行时加载的这个class文件去生成对象

    相关文章

      网友评论

          本文标题:JDK动态代理实践与原理

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