菜鸟详解JDK动态代理之美

来源:互联网 发布:淘宝旗舰店模板价格表 编辑:程序博客网 时间:2024/04/30 12:18

前言:
本文为菜鸟所写,大神请绕道。。。。

开始本文前我们首先明确一下如下几个概念:
1.首先理解一下什么是代理模式:为对象提供一种代理以控制对这个对象的访问。
2.为什么叫动态代理,个人的理解为代理的对象是在jvm运行时动态创建
3.动态代理的作用:实现AOP(在Spring,struts有大量应用)编程等。
4.JDK的动态代理要求:
要求被代理的对象必须实现一个接口,因为JDK动态代理生成的是一个实现了指定接口的一个代理对象,代理对象继承于Proxy类,由于java是不支持多重继承的,因此规定被代理对象必须实现某个接口,好用这个接口对象来接收我们生产的代理对象.

废话不多说,上代码

public interface Waiter {    public void service();}

接口的实现类ManWaiter

public class ManWaiter implements Waiter {    public void service() {        System.out.println("服务中。。。。");    }}

动态代理的测试类

public class demo1 {    public static void main(String[] args) {        new demo1().fun();    }    public void fun(){        ManWaiter waiter = new ManWaiter();        /*         * 准备三大参数         * classLoader :类加载器,将字节码文件加载到内存中         * interfaces : 指定需要实现的特定接口集合         * InvocationHandler : 代理对象方法的执行会被委托到这个对象的invoke方法         */        ClassLoader classLoader = this.getClass().getClassLoader();        Class[] interfaces = {Waiter.class};        InvocationHandler h = new WaiterInvocationHandler(waiter);         Waiter w = (Waiter)Proxy.newProxyInstance(classLoader, interfaces, h);        w.service();        //得到代理对象的字节码文件        String path = "D:/$Proxy0.class";          byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",                  ManWaiter.class.getInterfaces());         FileOutputStream out = null;          try {              out = new FileOutputStream(path);              out.write(classFile);              out.flush();          } catch (Exception e) {              e.printStackTrace();          } finally {              try {                  out.close();              } catch (IOException e) {                  e.printStackTrace();              }          }      }}class WaiterInvocationHandler implements InvocationHandler{    private Waiter waiter;    public WaiterInvocationHandler(Waiter waiter) {        this.waiter = waiter;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        System.out.println("你好");        waiter.service();        System.out.println("再见");        return null;    }}

class WaiterInvocationHandler implements InvocationHandler{
private Waiter waiter;
public WaiterInvocationHandler(Waiter waiter) {
this.waiter = waiter;
}

@Overridepublic Object invoke(Object proxy, Method method, Object[] args)        throws Throwable {    System.out.println("你好");    waiter.service();    System.out.println("再见");    return null;}

}`

运行结果:这里写图片描述

看到这里我们可能会有疑问,这个代理对象是如何生成的呢,动态代理的方法又是如何执行的,InvocationHandler类的实例又是起什么作用呢,在这里我们可以看到最后的代理对象是Proxy.newProxyInstance这个静态方法生成的,我们跟进这个方法看看,摘取关键源码分析。

 //通过类加载器,和接口得到这个类型对象Class<?> cl = getProxyClass0(loader, interfaces);
//拿到这个代理类的构造器final Constructor<?> cons = cl.getConstructor(constructorParams);
/*通过构造函数实例化这个类,得到一个动态代理的实例对象,并传入一个InvocationHandler类的实例ih,记住这一步*/return newInstance(cons, ih);

写到这我们对这个动态代理对象是如何得到的已经有了一个初步的了解这
个对象的创建了,但是关于InvocationHandler类的实例在这里起什么作用,代理类的方法是如何执行的,我们需要到这个代理类的源码中一探究竟。利用jd-gui打开我们生成的$Proxy0.class文件

import day28.Waiter;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;//这个类继承自Proxy,因此被代理对象必须实现某个接口,因为java不支持多继承public final class $Proxy0  extends Proxy  implements Waiter{//方法的赋值在下面的静态代码块里面  private static Method m1;  private static Method m4;  private static Method m3;  private static Method m0;  private static Method m2;  /*构造方法中传入了一个InvocationHandler类型的对象,我们上一步看的源码  *通过反射newInstance(cons, ih)得到这个$Proxy0的实例,传入了一个InvocationHandler的实例ih  *这个ih会作为形参传递给paramInvocationHandler  */  public $Proxy0(InvocationHandler paramInvocationHandler)  {    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 service()  {    try    {    /*此时方法的执行已经被委托给了h,也就是InvocationHandler这个类的实例h来调用    可能这里大家会有疑问这个h和我们构造方法传入的InvocationHandler的实例对象paramInvocationHandler    不一样,那是因为在这个类的构造方法里面调用了父类Proxy的构造,将paramInvocationHandler赋值给了h    */      this.h.invoke(this, m3, null);      return;    }    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 String toString()  {    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") });      m4 = Class.forName("day28.Waiter").getMethod("stopService", new Class[0]);      m3 = Class.forName("day28.Waiter").getMethod("service", new Class[0]);      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());    }  }}
写到这里相信大家对JDK动态代理应该有了一个比较深的理解了,而关于字节码文件如何生成,其实可以跟进上面源码提到的Class<?> cl = getProxyClass0(loader, interfaces);这个方法。便可以看到byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces),可以模仿这个方法得到我们代理对象的.class文件,再通过JD-gui等工具反编译得到我们的java文件