动态代理由浅入深

来源:互联网 发布:python string length 编辑:程序博客网 时间:2024/05/18 01:00

实际生活中,我们经常听说中介、代理商、加盟等关键名词。试想一下,为什么需要这些?
就说说中介:就我本人身在上海来说,如果我要卖房子,我想找中介。原因:我每天工作很忙,卖房子也不是一天两天能找到买家,除了我的工作外,我没有时间浪费在上面,给我减轻负担。
再说一些品牌商,他们的核心就是打响了品牌,使用技术早就了这些好的商品,但是这些商品需要卖出去,自己去卖?人力、精力都不够用。所以他们就需要找代理商帮他去卖。
基于上述的问题,其实体现了专业分工的思想。实际开发中,一个类中,方法除了当前需要的核心操作,其他次要的可以丢给代理类去做。这样分工明确,业务分明。这就是我们接下来要说的动态代理。

动态代理的作用:可以在不改变原有类的基础上对方法进行增强。
什么是动态代理?这里2个关键字:动态和代理。
首先说下代理:这里涉及到一种设计模式–代理模式。我先不解释代理模式,我先说个词语:代理商。那么这个词语相信大家都听过,这个代理商和我们这里的代理是一样的意思。
所以动态代理,就是java虚拟就动态的生成了这个代理,而不是我们开发者手写java代码生成的。

一:快速入门-动态代理

需求:卖的奶茶和制造的奶茶需要加上品牌名“一点点”
首先我们直接先看动态代理的实现:只要记住动态代理的实现就是一种格式,固定步骤。
首先需要一个接口:奶茶

public interface Milk {    //奶茶接口,有卖奶茶的方法    public void sell(String milkName);    //制造奶茶    public void make();}

然后一个品牌商:一点点

public class YiDianDian implements Milk {    @Override    public void sell(String milkName) {        System.out.println("卖奶茶");    }    @Override    public void make() {        System.out.println("制造奶茶");    }}

动态代理生成 一点点 的代理商

public class Test {    public static void main(String[] args) {        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");        //这是一点点 奶茶        final Milk yiDianDian = new YiDianDian();        //获取一点点的类加载器        ClassLoader classLoader = yiDianDian.getClass().getClassLoader();        //获取一点点实现的接口        Class<?>[] interfaces = yiDianDian.getClass().getInterfaces();        /*         * 动态代理创建一点点 的代理商         * 我们需要记住这种创建代理对象的格式:         *  第一个参数:类加载器,一般写被代理对象的类加载器         *  第二个参数:被代理对象实现的接口         *  第三个参数:处理器     InvocationHandler接口的实现类。实现invoke方法,用来指定代理做的事情。         *          */        Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {                /*             *              * 代理商做的事情就在这个invoke方法里书写,             * 这个方法3个参数:             * 第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。             * 第二个参数:正在执行的方法。这里可以理解成一点点这个品牌需要执行的方法。             * 第三个参数:正在执行的方法需要的参数。            */            @Override            public Object invoke(Object proxy, Method method, Object[] args)                    throws Throwable {                //输出执行的方法的名称                System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法");                //输出执行的方法下需要的参数                System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args));                //执行被代理对象本身方法原有的功能。                System.out.println("**********一点点品牌**********");                method.invoke(yiDianDian, args);                return null;            }        });        proxyYiDianDian.sell("红茶");        proxyYiDianDian.make();    }}

结果:
这里写图片描述

二:代理模式-静态代理

代理模式:创建一个对象的代理,以控制对这个对象的访问。通俗来讲,就是创建了一个中介(代理)。

首先需要一个接口:奶茶

public interface Milk {    //奶茶接口,有卖奶茶的方法    public void sell(String milkName);    //制造奶茶    public void make();}

然后一个品牌商:一点点

public class YiDianDian implements Milk {    @Override    public void sell(String milkName) {        System.out.println("卖奶茶");    }    @Override    public void make() {        System.out.println("制造奶茶");    }}

代理类

public class ProxyYiDianDian implements Milk {    //代理商  拥有   被代理商的引用    private Milk yiDianDain;    public ProxyYiDianDian(Milk milk) {        this.yiDianDain = milk;    }    @Override    public void sell(String milkName) {        System.out.println("***********一点点品牌*****************");        yiDianDain.sell(milkName);    }    @Override    public void make() {        System.out.println("***********一点点品牌*****************");        yiDianDain.make();      }}

测试:

public class Test2 {    public static void main(String[] args) {        //被代理对象   一点点品牌        Milk yiDianDian = new YiDianDian();        //代理对象  代理商        Milk proxyYiDianDian = new ProxyYiDianDian(yiDianDian);        //执行sell方法        proxyYiDianDian.sell("红茶");        //执行make方法        proxyYiDianDian.make();    }}

结果:
这里写图片描述

为什么需要动态代理:

就需求而言,制造和售出方法都添加打印品牌的名字,代理的原因就是因为我们所关注的售卖和制造的主要代码保持不变,创建代理类来在原有的功能上添加核心操作之外的步骤:打印一点点的品牌名。这样实现了每个方法的核心操作和打印品牌的业务分离。代码业务清晰。但是这个方法存在很明显缺点,当我们需要添加打印品牌的方法很多的时候,我们需要每一个方法都要添加这个输出语句,很是麻烦。而且后期一点点品牌发生变化,比如将一点点改成两点点,这是候每个方法都要修改,作为一个程序员真是心中十万只羊驼飘过。所以为了维护方便,我们需要使用动态代理。
其实这也就是说静态代理的缺点:
1.如果需要代理的方法很多的话,那么我们需要对每一个方法做实现,非常的麻烦。
2.后期如果接口增加新的方法,我们需要对java文件做改写。维护麻烦。
而动态代理完全避免了这些。因为这是java虚拟机做的事情。

动态代理和静态代理的区别:
通过上面的案例,我们发现,我们静态代理是需要我们手动的书写一个java文件,而动态代理是不需要手动书写一个java文件的。
我们学习java的都知道,我们创建一个对象,首先需要书写一个java文件,然后将java文件编译成class文件,类加载器将class文件加载到内存中。然后我们可以创建此对象的实例。而上述的代理商proxyYiDianDain这个对象是没有java文件的,也没有class文件,是java虚拟机直接在内存中生成了class对象.从而创建这个class对象的实例,当然这个实例可以按照我们开发者的需求做任何功能的改写,代替原有的对象。这就是动态代理含义的本质。
这里写图片描述

动态代理引用场景介绍:

通过代理模式,我们发现我们使用代理模式的目的是为了实现专业分工。所以在开发中当我们除了原始核心类以外的其他功能,比如说事务的管理,权限等,业务逻辑方法每个都要添加事务的管控和权限的判断是非常麻烦的,而这些东西,和我们方法的核心操作关系不大,所以我们可以将这些操作用专门的动态代理来处理。

三:动态代理深入理解-源码解析

1.Proxy.newProxyInstance静态方法解析

 public static Object newProxyInstance(ClassLoader loader,             Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{        if (h == null) {                throw new NullPointerException();        }        /*        * 1.核心操作,java虚拟机根据接口和类加载器,在内存中生成代理类的class对象。        */        Class<?> cl = getProxyClass0(loader, interfaces);         try {            //2.获取代理类的构造方法            final Constructor<?> cons = cl.getConstructor(constructorParams);            //3.获取我们调用这个方法传递过来的处理器。            final InvocationHandler ih = h;            SecurityManager sm = System.getSecurityManager();            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {                return AccessController.doPrivileged(new PrivilegedAction<Object>() {                public Object run() {                    return newInstance(cons, ih);                    }                });            } else {            //4.这里可以发现,通过代理类的构造方法和处理器来创建这个代理类的实例。也就是创建动态代理对象            return newInstance(cons, ih);            }        } catch (NoSuchMethodException e) {            throw new InternalError(e.toString());        }     }

通过上面的4步我们可以发现,其实动态代理,就是java虚拟机动态生成了一个class对象,然后创建了一个对象。
所以接下来我们看看java虚拟机动态生成的这个class对象的字节码文件

2.动态代理类的字节码文件
在测试类的最上面加上如下的一句代码,让java虚拟机生成的字节码文件输出到硬盘。
System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
然后再项目下添加如下目录:com/sun/proxy
这里写图片描述

执行代码:

public static void main(String[] args) {        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");        //这是一点点 奶茶        final Milk yiDianDian = new YiDianDian();        //获取一点点的类加载器        ClassLoader classLoader = yiDianDian.getClass().getClassLoader();        //获取一点点实现的接口        Class<?>[] interfaces = yiDianDian.getClass().getInterfaces();        /*         * 动态代理创建一点点 的代理商         * 我们需要记住这种创建代理对象的格式:         *  第一个参数:类加载器,一般写被代理对象的类加载器         *  第二个参数:被代理对象实现的接口         *  第三个参数:处理器     InvocationHandler接口的实现类。实现invoke方法,用来指定代理做的事情。         *          */        Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {                /*             *              * 代理商做的事情就在这个invoke方法里书写,             * 这个方法3个参数:             * 第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。             * 第二个参数:被代理对象本身所执行的方法。这里可以理解成一点点这个品牌需要执行的方法。             * 第三个参数:被代理对象本身所执行的方法需要的参数。            */            @Override            public Object invoke(Object proxy, Method method, Object[] args)                    throws Throwable {                //输出执行的方法的名称                System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法");                //输出执行的方法下需要的参数                System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args));                //执行被代理对象本身方法原有的功能。                System.out.println("**********一点点品牌**********");                method.invoke(yiDianDian, args);                return null;            }        });        proxyYiDianDian.sell("红茶");    }

可以发现,com/sun/proxy目录下生成了一个class文件,然后使用反编译工具打开,生成的代理类源码如下:

package com.sun.proxy;import com.itheima.proxy.Milk;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 Milk{  private static Method m1;  private static Method m3;  private static Method m0;  private static Method m2;  public $Proxy0(InvocationHandler paramInvocationHandler)    throws   {    super(paramInvocationHandler);  }  public final boolean equals(Object paramObject)    throws   {    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);    }  }  /**   * 可以发现,代理类的所有的方法都执行了this.h.invoke方法   */  public final void sell(String paramString)    throws   {    try    {      this.h.invoke(this, m3, new Object[] { paramString });      return;    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final int hashCode()    throws   {    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 String toString()    throws   {    try    {      return (String)this.h.invoke(this, m2, null);    }    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") });      m3 = Class.forName("com.xu.proxy.Milk").getMethod("sell", new Class[] { Class.forName("java.lang.String") });      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);      return;    }    catch (NoSuchMethodException localNoSuchMethodException)    {      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());    }    catch (ClassNotFoundException localClassNotFoundException)    {      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());    }  }}

通过上述的源码我们可以发现,代理类的所有的方法都执行了this.h.invoke()方法
我们将sell方法抽出来理解一下:

public final void sell(String paramString)    throws   {    try    {      /*       * sell方法的核心操作就是这个       * this代表当前对象,       * h 代表我们传递过来的 invacationHandler的实现类对象       * invoke就是处理器 的invoke方法       *        * 也就是说,最终的实现全部丢给了invacationHandler的invoke()方法处理了。       * 再来看看调用invoke()方法传递的参数,       *    this:代表当前对象,当前类就是代理类,所以是代理对象本身       *    m3: m3 = Class.forName("com.itheima.proxy.Milk").getMethod("sell", new Class[]                                                                                              { Class.forName("java.lang.String") });       *        最下面有如上代码,可以发现m3就是sell方法,执行的方法       *   new Object[] { paramString }:就是调用sell方法时传递的参数。      */      this.h.invoke(this, m3, new Object[] { paramString });      return;    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }

结合上面生成的动态代理类的sell方法的源码,结合我们一开始实现动态代理类的代码来看一下

Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {                /*                第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。             * 第二个参数:当前正在执行的方法。这里可以理解成一点点这个品牌需要执行的方法。             * 第三个参数:当前正在执行的方法需要的参数。         */@Override            public Object invoke(Object proxy, Method method, Object[] args)                    throws Throwable {                //输出执行的方法的名称                System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法");                //输出执行的方法下需要的参数                System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args));                //执行被代理对象本身方法原有的功能。                method.invoke(yiDianDian, args);                return null;            }        });

所以,执行代理对象的任何方法,就需要执行invocationHandler的invoke方法。所以在invoke方法中调用proxy对象的方法,就会调用代理对象本身的方法,而又回到了invoke()方法中。所以陷入死循环。