JDK动态代理深入探究

在上一篇文章设计模式之代理模式里,我们说了JDK动态代理类,不过我相信好多人都有疑惑,包括我,刚开始学的时候都是一脸懵逼,那么到底疑惑在哪里呢?

我的疑惑就是这个InvocationHandler的invoke方法到底是有啥用?我们都没有调用它。newProxyInstance返回的东西到底是啥?等等,这篇文章我们就一起来探讨一下吧。


首先我们先写一个简单的动态代理吧,有例子更好说明。还是买车的例子,哈哈,说明我挺想买车的。我就直接复制上篇文章的。

// Car.java
package com.codeliu.dao;

public interface Car {
    // vip客户可以直接买车
    public void buyCar();
}
// CarImp1.java
package com.codeliu.dao.impl;
import com.codeliu.dao.Car;
/**
 * 委托类1
 */
public class CarImp1 implements Car {
    public void buyCar() {
        System.out.println("我是vip1");
    }
}
// CarProxy.java
package com.codeliu.dao.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 动态代理类
 */
public class CarProxy implements InvocationHandler {
    private Object target;
    /**
     * 绑定一个委托对象并获得一个代理类对象
     * @param target [description]
     * @return [description]
     */
    public Object bind(Object target) {
        this.target = target;
        // 取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 
           this);
    }
    @Override
    //这个方法并不是我们自己去调用
    public Object invoke(Object proxy, Method method, Object[] args)
       throws Throwable {
        System.out.println("调用这个方法前:" + method);
        // 执行委托类的方法
        Object result = method.invoke(target,args);
        System.out.println("调用这个方法后:" + method);
        return result;
    }
}
package com.codeliu.test;

import com.codeliu.dao.impl.CarImp1;
import com.codeliu.dao.impl.CarImp2;
import com.codeliu.dao.impl.CarProxy;
import com.codeliu.dao.Car;
public class TestProxy {
    public static void main(String[] args) {
        CarProxy cp = new CarProxy();
        // 传入一个实现了该接口的实例就行
        Car car = (Car)cp.bind(new CarImp1());
        // Car car = (Car)cp.bind(new CarImp2());
        car.buyCar();
    }
}

好了,那么现在我们就来看看Test.java里生成的car对象到底是个什么东a西,是我们的Car接口吗?(我一开始一直理解不通)

我们利用反射可以获取到car对应类的大部分信息,好了,现在我们把Test.java改成如下形式

package com.codeliu.test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.codeliu.dao.Car;
import com.codeliu.dao.impl.CarImp1;
import com.codeliu.dao.impl.CarProxy;

public class Test {
    public static void main(String[] args) {
        CarProxy cp = new CarProxy();
        // 传入一个实现了该接口的实例就行
        Car car = (Car)cp.bind(new CarImp1());
        // Car car = (Car)cp.bind(new CarImp2());
        car.buyCar();
        
        System.out.println("car是Proxy类的子类吗?" + (car instanceof Proxy));
        Class<?> c = car.getClass();
        System.out.println(c);
        System.out.println("car的Class类是:" + c.getName());
        Field[] fields = c.getDeclaredFields();
        System.out.print("car的Class类中有哪些字段:");
        for(Field f:fields) {
            System.out.print(f.getName() + "   ");
        }
        System.out.println();
        System.out.print("car的Class类中有哪些方法:");
        Method[] methods = c.getDeclaredMethods();
        for(Method m:methods) {
            System.out.print(m.getName() + "   ");
        }
        System.out.println();
        System.out.println("car的Class类的父类是:" + c.getSuperclass());
        
        Class<?>[] class1 = c.getInterfaces();
        System.out.print("car的Class类实现的接口有:");
        for(Class<?> c1:class1) {
            System.out.println(c1.getName() + "  ");
        }
    }
}

运行一下,输出如下

调用这个方法前:public abstract void com.codeliu.dao.Car.buyCar()
我是vip1
调用这个方法后:public abstract void com.codeliu.dao.Car.buyCar()
car是Proxy类的子类吗?true
class com.sun.proxy.$Proxy0
car的Class类是:com.sun.proxy.$Proxy0
car的Class类中有哪些字段:m1   m2   m3   m0   
car的Class类中有哪些方法:equals   toString   hashCode   buyCar   
car的Class类的父类是:class java.lang.reflect.Proxy
car的Class类实现的接口有:com.codeliu.dao.Car  

恩,发现了什么,这个玩意竟然是一个Proxy类的子类,它的名字是$Proxy0,实现了我们自己定义的Car接口,还有四个字段和方法。



既然知道了上面的结果,那么我们就去看看newProxyInstance方法的源码,应该能找到答案了。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
}

我就截了比较重要的,getProxyClass0方法,它的作用就是生成一个proxy类,好,我们看看这个方法。

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

再去看看这个get方法,emmmmm,代码太多了,尼玛,我也看不太懂,不过经过我查资料,总结如下

newProxyInstance方法调用getProxyClass0生成一个$Proxy0类。
恩没了,现在我们要找答案就得去看看$Proxy0类的源码了。

因为生成的是一个.class文件,所以我们先反编译后,可以看到源码如下

package com.sun.proxy;

import com.codeliu.dao.Car;
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 Car
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  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 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 void buyCar()
  {
    try
    {
      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);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("com.codeliu.dao.Car").getMethod("buyCar", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

$Proxy0继承了Proxy类并实现了Car接口,它有一个构造方法,传入了一个InvocationHandler实例并调用了父类的构造方法,然后是四个final方法,其他三个是Object类传过来的,还有一个叫buyCar,这不就是我们在接口中定义的吗?我们看看它怎么写的

public final void buyCar()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

恩,首先m3在静态块中,取得Car接口中的buyCar方法,然后是调用invoke方法,666,现在终于解决了文章开头的疑惑,原来在这里调用了,等等,那么这个h又是什么东西,我们看看Proxy的源码

protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
}

一个字段和一个带参的构造函数,我们再看看$Proxy0的源码中有这么一段

public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

好家伙,到这里就真相大白了,虽然我还是不知道这个$Proxy0.class怎么生出来的,但基本也懂了,再总结一下整趟流程。

首先是我们写了一个接口,一个委托类实现了这个接口,然后写一个代理类实现InvocationHandler接口并重写invoke方法,之后调用Proxy的newProxyInstance方法,这个方法调用getProxyClass0方法生成一个动态代理对象,这个对象对应的类是$Proxy0,$Proxy0继承了Proxy类并且实现了我们自己写的接口,在这个类中,有静态字段和final修饰的方法,其中有我们在接口中定义的方法(为了好表述,我先把它叫做method),通过method方法,调用我们在代理类中重写的invoke,而我们在invoke方法中,又调用了委托类的方法,所以表面上好像是直接调用了委托类的方法,其实绕了一大圈,是系统帮我们调用了。



不知道大家有没有看懂,没有看懂就多看几遍,因为我说的也有点蒙圈了。今天连续写了两篇文章,好饿啊,不过弄懂了困扰我的问题,贼开心。

我是一个追求完美的人啊,我好想画一个流程图啊,不然一大段文字,不形象又难懂,不过我没力气了,有没有好心人画一个啊。==

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容

  • #我想你快乐# 00:06整个世界都是安静的,一个人窝在寝室的床上玩手机,除了室友偶尔的翻身声和窗外汽车的呼啸声...
    马滚滚阅读 250评论 0 0
  • 2.3性能:准确的元数据 指南2.3.7 - 性能 - 精准元 在App Store上所显示的程序名称或字幕包括关...
    景彧阅读 209评论 0 0
  • 很庆幸自己出生和成长在北方一个城市的一所院落,总以为家的样子是因为有屋有院才规矩方正延伸圆满。 尤其明显的是在过年...
    紫飞鱼21阅读 353评论 0 0
  • 什么样的小工具属于刚需?这样的刚需如何盈利?这样的需求其实是最适合我们业余时间去做的。 这次同样的也是通过百度搜索...
    Zero杂谈阅读 1,456评论 0 0
  • 今天是我开始写日记的第一天,我久久没有落笔,因为我不知道写些什么?我有时候在想我的工作很自由,怎么每天时间还是这么...
    刘梓涵妈妈阅读 219评论 0 0