java设计模式之代理模式

引子

今天在学netty相关的内容的时候,知道了netty是RPC(远程过程调用)的一种实现,就想深入了解一下RPC的相关只是,在看RPC的时候发现RPC框架又是基于代理模式实现的,为此兜兜转转就到了今天要讨论的内容,代理模式。找了相关的资料后,感觉有篇文章写得挺好,这里分享一下:https://segmentfault.com/a/1190000007089902

基本概念

代理模式

由上图可见,代理模式主要涉及下面几个部分:

  • 代理类 Proxy
  • 实际处理类 RealSubject
  • 代理类和实际处理类共同的接口 Subject
  • 方法调用方 Client
    通过引入代理类,防止调用方直接调用实际处理类的方法,从而实现解耦。
    下面就代理模式分为静态代理和动态代理两个方面来讨论。后面想学习的RPC远程调用就是基于代理模式实现的。

静态代理

首先举一个例子, 假设我们需要实现一个从不同存储介质(例如磁盘, 网络, 数据库)加载图片的功能, 那么使用静态代理的方式的话, 需要实现如下工作:

  1. 定义一个加载图片的接口
  2. 实现实际操作对象(LoadFromDisk, LoadFromNet, LoadFromDB)
  3. 实现代理对象
timg.jpg

首先定义加载图片的接口:

public interface LoadImage {
    Image loadImage(String name);
}

接着编写代理类:

public class LoadImageProxy implements LoadImage {
    private LoadImage loadImageReal;
    public LoadImageProxy(LoadImage loadImageReal) {
        this.loadImageReal = loadImageReal;
    }
    @Override
    public Image loadImage(String name) {
        return loadImageReal.loadImage(name);
    }
}

代理类的构造方法引入实际处理类的实例,在复写的loadImage中执行实际处理类的具体方法。所以调用方调用如下:

public class App {
    public static void main(String[] args) {
        LoadFromDisk loadFromDisk = new LoadFromDisk();
        LoadImageProxy proxy = new LoadImageProxy(loadFromDisk);
        proxy.loadImage("/tmp/test.png");
    }
}

根据代理模式, 我们在上面的代码中展示了一个基本的静态代理的例子, LoadImageProxy 是代理类, 它会将所有的接口调用都转发给实际的对象, 并从实际对象中获取结果. 因此我们在实例化 LoadImageProxy 时, 提供不同的实际对象时, 就可以实现从不同的介质中读取图片的功能了。

动态代理

所谓动态代理就是动态地创建代理并且动态地处理所代理对象的方法调用
在 Java 的动态代理中, 涉及两个重要的类或接口:

  • Proxy
  • InvocationHandler
    下面就先看下这两个类的内容。

关于 Proxy 类

Proxy 主要是提供了 Proxy.newProxyInstance 静态方法, 其签名如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

参数如下:

  • loader: 即类加载器, 指定由哪个ClassLoader对象来对生成的代理对象进行加载
  • interfaces: 一个Interface对象的数组, 表示的是代理对象所需要实现的接口
  • h: 即 InvocationHandler 的实现对象. 当调用代理对象的接口时, 实际上会 通过 InvocationHandler.invkoe 将调用转发给实际的对象.

关于 InvocationHandler 接口

我们在前面提到过, 在调用 Proxy.newProxyInstance 方法时, 需要传递一个 InvocationHandler 接口的实现对象, 那么这个 InvocationHandler 接口有什么用呢?
实际上, 在 Java 动态代理中, 我们都必须要实现这个接口, 它是沟通了代理对象和实际对象的桥梁, 当我们调用了代理对象所提供的接口方法时, 此方法调用会被封装并且转发到 InvocationHandler.invoke 方法中, 在 invoke 方法中调用实际的对象的对应方法。
InvocationHandler.invoke 方法的声明如下:

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

这个方法接收三个参数:

  • proxy: 指代理对象, 即 Proxy.newProxyInstance 所返回的对象(注意, proxy 并不是实际的被代理对象)
  • method: 我们所要调用真实对象的方法的 Method 对象
  • args: 调用真实对象某个方法时接受的参数

动态代理例子

使用动态代理的步骤很简单, 可以概括为如下两步:

  1. 实现 InvocationHandler 接口, 并在 invoke 中调用真实对象的对应方法
  2. 通过 Proxy.newProxyInstance 静态方法获取一个代理对象.
    下面就动态代理再举个例子:


    timg.jpg

    首先还是用之前加载图片的接口:

public interface LoadImage {
    Image loadImage(String name);
}

接着实现InvocationHandler接口:

public class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(proxied, args);
    }
}

可以看到, 在实现的 invoke 方法中, 我们简单地通过 method.invoke(proxied, args) 来调用了真实对象的方法.
有了 InvocationHandler 后, 我们就可以创建代理对象并通过代理对象来操作实际对象了:

public class App {
    public static void main(String[] args) {
        // 实际对象
        LoadFromDisk loadFromDisk = new LoadFromDisk();
        // 通过 Proxy.newProxyInstance 静态方法创建代理对象
        LoadImage loadImage = (LoadImage) Proxy.newProxyInstance(LoadImage.class.getClassLoader(), new Class[]{LoadImage.class}, new DynamicProxyHandler(loadFromDisk));

        // 通过代理对象操作实际对象.
        loadImage.loadImage("/tmp/test.png");
    }
}

上述代码中,当我们执行了代理类实例的loadImage方法时,会转而执行实际处理类LoadFromDisk的具体方法。而且,如果实际处理类有很多方法,通过执行代理类的方法的时候,都先会通过执行DynamicProxyHandler 这个类的invoke方法再去调用实际处理类的具体方法。所以比如如果想在实际处理类的每个方法执行前后都加个打印日志就只要在InvocationHandler的invoke方法中加个日志就行了,而不必在实际处理类的每个方法内部都加上打印,这就是动态代理相比于静态代理的优势。

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

推荐阅读更多精彩内容