美文网首页
动态代理

动态代理

作者: wly25 | 来源:发表于2020-07-03 11:35 被阅读0次

    https://www.cnblogs.com/incognitor/p/9759987.html

    #### 1. 动态代理的实现方式:

    涉及3个类:

    - 委托类对象/被代理对象 - 实际对象

    - 实现InvocationHandler对象 - 包含对实际对象方法增强的实现

    - 代理对象 Proxy.newProxyInstance(zhangsan.getClass().getClassLoader(),zhangsan.getClass().getInterfaces(), stuHandler);

    #### 2. 动态代理的原理

    1. newProxyInstance 封装创建代理对象的步骤 Class<?> cl = getProxyClass0(loader, intfs);

    2. 在运行的过程中通过ProxyGenerator创建代理类的字节码文件并加载到JVM中,这样就获取到代理类的Class对象。 3. 通过class对象 通过反射获取构造函数对象(需要传入InvocationHandler), 通过构造函数创建代理类实例对象

    3. 反编译代理类的字节码文件 代理类以$Proxy0来命名 且extends Proxy implements Person 继承Proxy类实现了和委托类相同的接口

    extends Proxy 集成了父类的构造函数, 父类构造函数需要传入一个InvocationHandler对象, 这个就是我们在Proxy.newProxyInstance传入的nvocationHandler对象

    实现了被代理对象相同的接口, 这样就和被代理对象有相同的方法, 可以通过代理类调用相同方法。

    4. 代理的构造函数会传入一个InvocationHandler的实例  而InvocationHandler有持有一个委托类的实例

    5. 代理类的内部会会通过反射维护接口所有方法实例 Method m1, m2

    6. 所以代理类也可以调用和委托类相同的方法 因为实现了相同的接口  当通过代理类调用方法是 实际是上调用this.h.invoke(this, m3, null); 也就是InvocationHander的invoke方法 传入当前的invocationhandler实例和 对应的方法Methond, 以及参数

    7. 这样 invoke方法执行增强代码部分 然后在通过其维护的委托类实例调用m3方法也就是 委托类的方法  这样就实现了动态代理

    3. #### CGLib

    Java原生的动态代理 只能代理实现接口的类  使用cglib来实现对继承类的动态代理  当然cdglib是不能代理final类的

    ```

    package com.learn.proxy.inteface;

    public interface IActor {

    public void basicAct(float money);

    public void dangeAct(float money);

    }

    package com.learn.proxy.inteface;

    public class Actor implements IActor{

    public void basicAct(float money) {

      System.out.println("拿到钱 开始基本表演 " + money);

    }

    public void dangeAct(float money) {

      System.out.println("拿到钱 开始危险表演 " + money);

    }

    }

    package com.learn.proxy.inteface;

    import java.lang.reflect.InvocationHandler;

    import java.lang.reflect.Method;

    import java.lang.reflect.Proxy;

    /**

    * 动态代理:

    作用:不改变源码的基础上 对已有的方法增强 (它是AOP思想的实现技术)

    分类:

      基于接口的动态代理

      要求: 被代理对象最少实现一个接口

      提供者: JDK 官方

      设计的类:Proxy

      创建代理类的方法: newProxyInstance(ClassLoader, class[], InvocationHandler)

        参数的含义:

        ClassLoader:类加载器 和被代理对象使用相同的类加载器 一般都是固定写法 比如要代理xxx 写法就是xxx.getClass().getClassLoader()

        Class[]: 字节码数组 被代理类实现的接口 要求被代理对象和代理对象具有相同的行为 一般也是固定写法 xxx.getClass().getInterfaces()

                InvocationHandler: 它是一个接口 就是用于我们提供增强代码的  我们一般都是些一个该接口的实现类 实现类可以是匿名内部类

        它的含义就是 如何代理 此处代码只能谁用谁提供

        策略模式:

          使用要求: 数据已经有了  目的明确  达到目标的过程就是侧罗

          在dbutils中的ResultSetHandler就是策略模式的具体应用

    * @author LJ

    *

    */

    public class Client {

    public static void main(String[] args) {

      final Actor actor = new Actor();

    // actor.basicAct(1000f);

    // actor.dangeAct(5000f);

     

      IActor proxyActor = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler(){

      /**

        * 执行被代理对象任何方法 都会先执行下面的方法 该方法有类似拦截的功能

        * 方法参数:

        *  Object proxy: 代理对象的引用    不一定每次都用

        *  Method method: 当前执行的方法

        *  Object[]args: 当前执行方法所需要的参数

        * 

        *  返回值: 当前执行方法的返回值

        */

      @Override

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object rtValue = null;

        // 1. 取出方法中的参数

        Float money = (Float) args[0];

        // 2. 判断当前执行的是什么方法

        if("basicAct".equals(method.getName())) {

        //基本表演

        if(money > 10000) {

          rtValue = method.invoke(actor, money);

        }

        }

        if("dangeAct".equals(method.getName())) {

        //基本表演

        if(money > 50000) {

          rtValue = method.invoke(actor, money);

        }

        }   

        return rtValue;

      } 

      });

     

      proxyActor.basicAct(20000f);

      proxyActor.dangeAct(150000f);

    }

    }

    ```

    ### 引入场景

    动态代理在Java中是很重要的一部分,在很多框架中都会用到,如Spring中的AOP、Hadoop中的RPC等。

    如果要对第三方提供的JAR包中的某个类中的某个方法的前后加上自己的逻辑,比如打log ,注意此时我们只有第三方提供的CLASS文件,因此根本不可能去修改别人的源代码,那该怎么办?

    有两种方法可以实现,一种是利用继承,另一种是利用聚合。举例说明:

    假设第三方中提供一个Run接口,里面只一个run方法,以及它的实现类Person

    ```

    public interface Run { 

        public void run(); 

    }

    public class Person implements Run { 

        @Override 

        public void run() { 

            System.out.println("person running..."); 

        } 

    }

    ```

    第一种利用继承实现

    ```

    public class SuperMan1 extends Person {   

        @Override 

        public void run() { 

            //方法开始前打LOG 

            LOG.info("super man1 before run..."); 

            super.run(); 

            //方法结束后打LOG 

            LOG.info("super man1 after run..."); 

        }   

    }

    ```

    第二种利用聚合实现

    ```

    public class SuperMan2 implements Run { 

        private Run person;   

        public SuperMan2(Run person) { 

            this.person = person; 

        } 

     

        @Override 

        public void run() { 

            //方法开始前打LOG 

            LOG.info("super man2 before run..."); 

            person.run(); 

            //方法结束后打LOG 

            LOG.info("super man2 after run..."); 

        }   

    }

    ```

    这两种实现方式,哪一种更好呢?

    显然是第二种利用聚合实现方法好,因为这种方式很灵活,同时又不会有多层的父子类关系。而继承最不好的地方就是不灵活,同时会很容易形成臃肿的父子类关系,不利于后期的维护

    其实SuperMan1类和SuperMan2类都是Person类的代理类,对Person类中的方法进行加强,只不过这种代理是静态代理,很受限制。因为SuperMan1和SuperMan2只能代理Run类型的类,其它类型没法代理。如果有其他接口比如Fly 就需要额外在写代理类,有多少个接口就要单独实现多少个代理类。 

    为了解决这个问题,Java中引入动态代理。

    ### Java动态代理原理

    **动态代理的意思就是一个类的(比如Person)的代理类(比如SuperMan2)是动态生成的,也就是说这个代理类不是提前写好的,是在程序运行时动态的生成的**

    动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量

    一个典型的动态代理创建对象过程可分为以下四个步骤:

    1、通过实现InvocationHandler接口创建调用处理器

    ```

    IvocationHandler handler = new InvocationHandlerImpl(...);

    ```

    2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类

    ```

    Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

    ```

    反编译代理类字节码

    ```Java

    import com.xych.proxy.jdkproxy.IDao;

    import java.lang.reflect.InvocationHandler;

    import java.lang.reflect.Method;

    import java.lang.reflect.Proxy;

    import java.lang.reflect.UndeclaredThrowableException;

    public final class $Proxy0 extends Proxy implements IDao {

        private static Method m1;

        private static Method m4;

        private static Method m2;

        private static Method m0;

        private static Method m3;

        public $Proxy0(InvocationHandler paramInvocationHandler) throws Throwable  {

            super(paramInvocationHandler);

        }

        public final boolean equals(Object paramObject)  {

            try {

                return ((Boolean) this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();

            } catch(Error | RuntimeException localError) {

                throw localError;

            } catch(Throwable localThrowable) {

                throw new UndeclaredThrowableException(localThrowable);

            }

        }

        public final void save() {

            try {

                this.h.invoke(this, m4, null);

                return;

            } catch(Error | RuntimeException localError) {

                throw localError;

            } catch(Throwable localThrowable) {

                throw new UndeclaredThrowableException(localThrowable);

            }

        }

        public final String toString()  {

            try {

                return (String) this.h.invoke(this, m2, null);

            } catch(Error | RuntimeException localError) {

                throw localError;

            } catch(Throwable localThrowable)  {

                throw new UndeclaredThrowableException(localThrowable);

            }

        }

        public final int hashCode()  {

            try {

                return ((Integer) this.h.invoke(this, m0, null)).intValue();

            } catch(Error | RuntimeException localError) {

                throw localError;

            } catch(Throwable localThrowable) {

                throw new UndeclaredThrowableException(localThrowable);

            }

        }

        public final void update() {

            try

            {

                this.h.invoke(this, m3, null);

                return;

            }catch(Error | RuntimeException localError) {

                throw localError;

            } catch(Throwable localThrowable) {

                throw new UndeclaredThrowableException(localThrowable);

            }

        }

        static  {

            try {

                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });

                m4 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("save", new Class[0]);

                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

                m3 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("update", new Class[0]);

                return;

            } catch(NoSuchMethodException localNoSuchMethodException)  {

                throw new NoSuchMethodError(localNoSuchMethodException.getMessage());

            } catch(ClassNotFoundException localClassNotFoundException)  {

                throw new NoClassDefFoundError(localClassNotFoundException.getMessage());

            }

        }

    }

    ```

    $Proxy0继承Proxy继承,在Proxy里有一个成员变量h。并通过构造函数复制。 这个h就是在newProxyInstance传入的

    ```

    protected InvocationHandler h;

    ```

    jdk在程序运行过程中动态为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类字节码文件,然后编译到JVM中 这样就获取到$Proxy0的class对象

    然后通过class对象 通过反射机制获取动态代理类的构造函数,其参数类型InvocationHandler接口类型

    ```

    Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

    ```

    通过构造函数创建代理类实例,此时需传入已经实现InvocationHandler接口的对象作为参数被传入 这样就创建了代理类的实例对象

    ```

    Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

    ```

    为了简化对象创建过程,Proxy类中的newProxyInstance方法封装了2~4,只需两步即可完成代理对象的创建。

    ```

    Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),

            new Class[]{Subject.class}, new InvocationHandlerImpl (real));

    ```

    通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。

    首先来看static代码块

    ```

    static  {

            try {

                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });

                m4 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("save", new Class[0]);

                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

                m3 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("update", new Class[0]);

                return;

            } catch(NoSuchMethodException localNoSuchMethodException)  {

                throw new NoSuchMethodError(localNoSuchMethodException.getMessage());

            } catch(ClassNotFoundException localClassNotFoundException)  {

                throw new NoClassDefFoundError(localClassNotFoundException.getMessage());

            }

        }

    ```

    IDao接口定义了两个方法save、update,加上Object类的equals、toString、hashCode这三个方法,总共5个方法,即上面代码中的m1-m5成员变量,并在’静态代码块’里初始化了这5个成员变量。

    - m0-m3 反射获取Object类中的三个方法: quals、toString、hashCode

    - m4-mxx 反射获取实现接口中的方法

    同时$Proxy0本身也实现了IDao接口并重写了equals、toString、hashCode方法

    ```

    public final class $Proxy0 extends Proxy implements IDao`

    ```

    而$Proxy0本身有h成员变量,代理类调用自己这些方法时 都是通过统一调用this.h.invoke方法类执行

    我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中传入了对应的方法m0-mxx

    ```

    this.h.invoke(this, m3, null);

    ```

    而在通过反射执行方法时 

    ```

    rtValue = method.invoke(actor, money);

    ```

    实际执行了被代理对象actor的相应方法。

    通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。

    ,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

    相关文章

      网友评论

          本文标题:动态代理

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