美文网首页
动态代理

动态代理

作者: ggghhhhhhhh | 来源:发表于2020-06-10 09:20 被阅读0次

    要理解动态代理之前,我们先来聊聊代理模式。

    1.静态代理

    有一天,接到产品经理的需求,在这类的所有方法前后都打上日志。

    如何能在不修改这个类(假设类设计都满足依赖倒转原则,即都有对应的接口)的前提下实现这个需求呢?

    我们首先想到的就是代理模式,代理模式的定义是,给某一个类提供一个代理类,并由代理类来控制远对象的引用,通俗来讲就是中介。

    假设我们原来有一个

    public interface IPerson {
        void shopping();
    }
    
    public class Person implements IPerson {
    
        private String name;
        public Person(String name) {
            this.name = name;
        }
    
        @Override
        public void shopping() {
            System.out.println(name + "买了个表e");
        }
    }
    

    我们需要在shopping前后打log,于是我们需要一个代理类

    public class PersonProxy implements IPerson {
        
        private IPerson source;
    
        public PersonProxy(IPerson source) {
            this.source = source;
        }
    
        @Override
        public void shopping() {
            System.out.println("方法执行前打印");
            source.shopping();
            System.out.println("方法执行后打印");
        }
    }
    

    然后加上代理类

    public static void main(String[] args) {
            //原有类
            IPerson zhangsan=new Person("张三");
            //代理类
            IPerson proxy=new PersonProxy(zhangsan);
            //由代理类执行
            proxy.shopping();
        }
    
    打印
    方法执行前打印
    张三买了个表
    方法执行后打印
    

    可以看到这样就已经实现了产品经理的需求了~~~

    这就是代理模式中的一种,也可以叫做静态代理

    静态代理的优缺点:

    • 优点:客户端不需要知道实现类怎么做的,只需要知道代理类即可,主需要专注本身的逻辑,而不需要关注太多外部的影响。
    • 缺点:静态代理的缺点也很明显, 每个代理类只服务一个类,如果需要服务更多的类的时候,势必要为每一个类进行代理。

    2.动态代理

    有一天产品经理又说,上次的功能做的不错,现在又有一个新的需求,把所有类的所有方法前后,都打上日志。

    哦豁~~~

    如果还是按照静态代理的做法,我们需要为每一个类都实现一个代理类,这成百上千个类,是要搞死人的说。

    有没有一些更加方便的方法呢,答案是有的,就是使用动态代理

    与静态代理的区别,静态代理是需要我们手动去实现一个代理类的,而相反,动态代理则是可以让系统帮我们动态生成一个代理类。这么神奇?下面我们就来了解一下。

    Java的java.lang.reflect包下提供了一个Proxy类和InvocationHandler接口,通过这个类和接口,可以动态生成JDK动态代理类和动态代理对象

    创建动态代理的步骤:

    //1.创建于代理对象相关联的InvocationHandler
    InvocationHandler handler=new MyInvocationHandler<>(zhangsan);
    //2.使用Proxy的getProxyClass获得代理类
    Class<?> proxyClass = Proxy.getProxyClass(Person.class.getClassLoader(),   Person.class.getInterfaces());
    //3.获得生成的代理类的 参数问InvocationHandler.class的构造函数
    Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
    //4.通过构造函数新建一个实例
    Person proxyObj = (Person) constructor.newInstance(handler);
    

    当然以上步骤Proxy集合在一起提供了一个更加方便调用的方法

    //1.创建于代理对象相关联的InvocationHandler
    InvocationHandler handler=new MyInvocationHandler<>(zhangsan);
    
    //2.通过Proxy的newProxyInstance创建一个代理对象,来代理zhangsan,代理对象的每个执行方法都会替换执行InvocationHandler中的invoke方法
    IPerson proxy = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(), IPerson.class.getInterfaces(), handler);
    

    先来看具体的代码
    Person类省略,参考上面静态代理的代码

    InvocationHandler类

    public class MyInvocationHandler<T> implements InvocationHandler {
        /**
         * 被代理的类
         */
        T target;
    
        public MyInvocationHandler(T target) {
            this.target = target;
        }
    
        /**
         *
         * @param proxy 代表动态代理对象
         * @param method 正在执行的方法
         * @param args 代表调用目标方法时传入的参数
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("代理执行"+method.getName()+"方法");
            //代码插入日志,这里只是为了方便,就直接打印了
            System.out.println(method.getName()+"方法执行前打印");
            Object invoke = method.invoke(target, args);
            System.out.println(method.getName()+"方法执行后打印");
            return invoke;
        }
    }
    

    动态代理实现

    //创建被代理对象
    IPerson zhangsan=new Person("张三");
    
    //创建于代理对象相关联的InvocationHandler
    InvocationHandler handler=new MyInvocationHandler<>(zhangsan);
    
    //创建一个代理类,来代理zhangsan
    IPerson proxy = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(), IPerson.class.getInterfaces(), handler);
    
    //使用代理类执行方法
    proxy.shopping();
    

    看看上面的打印


    打印

    可以看到,使用动态代理创建的代理类执行方法,正常的完成了我们的打日志功能。

    看到这里,我们不禁地有些疑问,为什么系统可以帮我们动态地实现代理类,他是如何做到的呢?带着疑问,我们来探究一下动态代理的原理

    3.动态代理的原理初探

    先要谈及原理,必然需要先从源码开始,实现动态代理很简单,核心代码只有一句,Proxy.newProxyInstance,我们先来看看这个方法里面的实现。

    private static final Class<?>[] constructorParams =
            { InvocationHandler.class };
    
    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
            //1.先克隆需要代理的接口类信息
            final Class<?>[] intfs = interfaces.clone();
            //2.通过getProxyClass0得到代理类
            Class<?> cl = getProxyClass0(loader, intfs);
    
            try {
                 //3.获取代理类的构造函数(参数为InvocationHandler.class)
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    cons.setAccessible(true);
                }
                //4.获取代理类的实例
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }
    

    从代码中可以看出,跟我们上面分析的步骤一样,重点是getProxyClass0方法,得到一个代理类,这里是获取代理类的关键,由于是动态生产了文件,具体代码就不想去分析了,这个类暂存在jvm虚拟机中的,我们可以通过下面的方法获取字节码文件

    byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Person.class.getInterfaces());
            String path = "E:/PersonProxy.class";
            try(FileOutputStream fos = new FileOutputStream(path)) {
                fos.write(classFile);
                fos.flush();
                System.out.println("代理类class文件写入成功");
            } catch (Exception e) {
                System.out.println("写文件错误");
            }
    

    ProxyGenerator在jdk的tl.jar包上,android studio无法直接访问到,于是使用了
    IntelliJ IDEA 打印出来。

    下面是系统生成的代理类的大概代码

    /**
    *  可以看到系统帮我们生产了一个$Proxy0类,并且实现了IPerson接口,也就是Person的代理类
    */
    public final class $Proxy0 extends Proxy implements IPerson {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        //其实看到这个构造函数的时候,就已经瞬间明白了,InvocationHandler 相当于被代理类
        //传入到代理类中,让代理类去处理具体事情,跟上面所描述的静态代理一样
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        //在来看看这个静态代码块,主要关注m3,通过反射获取IPerson.shopping()这个方法
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("cn.lgh.mydemo.IPerson").getMethod("shopping");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    
        ...
    
        public final void shopping() throws  {
            try {
                //super.h也就是我们通过构造函数传进来的InvocationHandler
                //然后执行了invoke方法,并且把m3(上面反射得到的shopping方法)传过去,
                //然后我们就可以在InvocationHandler中的invoke得到被代理类的真实方法了
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        ...
    }
    

    看到这里,我们是不是已经明白了动态代理原理了。
    jdk生成了$Proxy0(这里0的意思是,如果有多个代理类,就递增显示,默认从0开始)的代理类,这个类存放在内存中,当我们创建代理对象是,就是通过反射找到这个类的构造函数,然后创建这个类实例。通过查看这个类的源码,我们不难了解动态代理的原理。

    4.总结

    1.动态代理是对代理模式的实现
    2.代理模式包括静态代理和动态代理
    3.动态代理原理是通过Proxy这个类,让系统生成了一个代理类,并且把中介对象InvocationHandler传入代理列中,中介这对象持有被代理对象,当Proxy执行具体方法时,调用中介者(InvocationHandler)的invoke方法,然后我们在InvocationHandler的invoke方法中,调用被代理在的具体方法,达到代理的目的。

    相关文章

      网友评论

          本文标题:动态代理

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