动态代理之InvocationHandler && Proxy

声明:文章仅是为了记录自己的学习成果和分享给大家,参考了这篇文章http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 思路大致一样。不过自己又重新写了一遍,一是增强一下印象,二是写的过程中也有一些新的思考。

代理类与被代理类

被代理类,简单说来就是 需要被增强 的类,增强的理解,简单而言就是在调用前后增加了一些其他操作。代理可以分为静态代理和动态代理。
代理类,假如我们称被代理类为真实的对象,那么代理类就是一个代理对象。我们在调用代理对象的方法的时候,代理对象会自动调用真实对象的相应方法,并对其做出特定的增强。
静态代理,类似于装饰者模式,包装类和被包装类实现了同一个接口,用于统一类型。这样就可以针对于接口中的某一个方法做增强了。但是,如果接口暴露的方法太多,而我们又需要对多个接口做强化,静态代理就显得太麻烦了。
动态代理,在运行时,通过反射的方法,动态的进行方法的增强。动态代理又分为好几种呢!这里只介绍jdk自带的!

注:这只是静态代理和动态代理的小区别,其他的区别在本篇目暂不做深究。

InvoCationHander和Proxy

*** Proxy***,即代理类,它继承自Object类。

InvoCationHander 代理小助手(这个词是我乱翻译的 - - ),他有一个重要的方法,叫做invoke;


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

文档对它的描述:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
大意是:InvocationHandler是一个,它被被proxy实例实现。每一个proxy实例都与一个handler相关联。当一个proxy实例所代理方法被调用的时候,将对此次调用进行编码(我的理解就是,对原方法的实现过程重新进行梳理),并将其分发到InvocationHandler中的invoke方法中去(只要调用一次proxy的方法,就会调用一次InvocationHandler的invoke方法)。

抓重点:

  1. 一个proxy与一个InvocationHandler关联;
  2. 调用proxy的方法(自然也包括proxy的父类Object的方法如toString啦),会转到InvocationHandler 的 invoke方法上去;
  3. 2017/05/23补充:代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。(参考于https://www.ibm.com/developerworks/cn/java/j-lo-proxy2/ 这篇文章写的比我不知高到哪里去了..)

一个简单的实现过程###

先定义一个接口IAnimal,暴露一个cry方法。
为什么需要一个接口?自然是统一类型呀。
统一类型以后,proxy和被代理类继承了接口的暴露方法。这样,当我们调用的proxy的方法时,可以代理被代理类中同名方法了。否则,如果要调用被代理类的cry方法,你的proxy难道还要写一个新方法,而且这个新方法又要叫做什么名字呢?
但是,强制要求被代理类也要实现同样的接口。对于未实现该接口的类,代理类将无能为力。

public interface IAnimal {
    void cry();
}

再来一个实现类,也就是我们的被代理类,或者说真实对象,即被增强的类。


public class Pig implements IAnimal {

    @Override
    public void cry() {
        System.out.println("喵~喵~喵~~~");
    }

}

下面该轮到代理小助手上场了。
注意,bind是自己新加的一个方法,这是为了便于保存被代理对象的引用和生成代理类实例;
invoke方法中的第一个参数proxy指的是代理对象,其实也就是bind方法生成的对象啦,而并不是AnimalProxy本身,否则为嘛要多给出来一个参数,用this就可以了嘛!

public class AnimalProxy implements InvocationHandler {
    //被代理对象的引用
    private Object realObj = null;
    
    public Object bind(Object target) {
        this.realObj = 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("-------鉴定假猪过程开始,请听猪叫-------");
        Object invoke = method.invoke(realObj, args);
        System.out.println("-------鉴定过程结束-------");
        return invoke;
    }
    
}

来测试一下

    public static void main(String[] args) {
        AnimalProxy animalProxy =new AnimalProxy();
        IAnimal animal = (IAnimal) animalProxy.bind(new Pig());
        animal.cry();
    }

控制台打印结果

-------鉴定假猪过程开始,请听猪叫-------
喵~喵~喵~~~
-------鉴定过程结束-------

假如,修改一下代理小助手的实现类中的invoke,多加了一行代码,打印proxy对象

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

       System.out.println("-------鉴定假猪过程开始,请听猪叫-------");

       Object invoke = method.invoke(realObj, args);
       System.out.println( proxy);//多加了这行代码

       System.out.println("-------鉴定过程结束-------");

       return invoke;
   }

看看打印结果

-------鉴定假猪过程开始,请听猪叫-------
-------鉴定假猪过程开始,请听猪叫-------
-------鉴定假猪过程开始,请听猪叫-------
-------鉴定假猪过程开始,请听猪叫-------
...
Exception in thread "main" java.lang.StackOverflowError
    at java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:52)
    at java.nio.CharBuffer.wrap(CharBuffer.java:350)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:246)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:106)
    at java.io.OutputStreamWriter.write(OutputStreamWriter.java:190)
    at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:111)
    at java.io.PrintStream.write(PrintStream.java:476)
    at java.io.PrintStream.print(PrintStream.java:619)
    at java.io.PrintStream.println(PrintStream.java:756)
    at AnimalProxy.invoke(AnimalProxy.java:34)
    at com.sun.proxy.$Proxy0.toString(Unknown Source)
    at java.lang.String.valueOf(String.java:2826)
    
...

嗯哼?!堆栈溢出了?为啥??
原因是因为在代理小助手那里,打印了proxy对象,proxy是啥?是代理类的实例啊,还记得么?只要调用一次proxy实例的方法,就又要进入invoke方法一次。what?没调用呀?非也,打印的时候,自动调用了对象的toString()了。so,卡在这死循环了。

以上。

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

推荐阅读更多精彩内容