资讯专栏INFORMATION COLUMN

Java 动态代理(Dynamic proxy) 小结

Jason / 3398人阅读

摘要:代理模式基本概念不论是静态代理还是动态代理其本质都是代理模式的一种实现那么什么是代理模式呢代理模式即给某一个对象提供一个代理并由代理对象控制对原对象的引用代理模式其实取材于实际生活例如我们生活中常见的房屋租赁代理我们在租房时一般不是直接和房

代理模式 基本概念

不论是静态代理还是动态代理, 其本质都是代理模式的一种实现, 那么什么是代理模式呢?
代理模式, 即给某一个对象提供一个代理, 并由代理对象控制对原对象的引用.
代理模式其实取材于实际生活, 例如我们生活中常见的房屋租赁代理, 我们在租房时, 一般不是直接和房东打交道, 而是和中间商打交道, 即中间商代理了房东, 我们通过中间商完成与房东的间接沟通.
代理模式主要涉及三个角色:

Subject: 抽象角色, 声明真实对象和代理对象的共同接口.

Proxy: 代理角色, 它是真实角色的封装, 其内部持有真实角色的引用, 并且提供了和真实角色一样的接口, 因此程序中可以通过代理角色来操作真实的角色, 并且还可以附带其他额外的操作.

RealSubject: 真实角色, 代理角色所代表的真实对象, 是我们最终要引用的对象.

这三个角色的 UML 图如下(图片引用自维基百科)

代理模式的优点

代理模式能够协调调用者和被调用者, 在一定程度上降低了系统的耦合度.

代理模式可以提供更大的灵活性

代理模式的缺点

由于在客户端和真实主题之间增加了代理对象, 因此有些类型的代理模式可能会造成请求的处理速度变慢

实现代理模式需要额外的工作, 有些代理模式的实现 非常复杂

代理模式的常用实现

远程代理(remote proxy): 用本地对象来代表一个远端的对象, 对本地对象方法的调用都会作用于远端对象. 远程代理最常见的例子是 ATM 机, 这里 ATM 机充当的就是本地代理对象, 而远端对象就是银行中的存取钱系统, 我们通过 ATM 机来间接地和远端系统打交道.

虚拟代理(virtual proxy): 虚拟代理是大型对象或复杂操作的占位符. 它常用的场景是实现延时加载或复杂任务的后台执行. 例如当一个对象需要很长的时间来初始化时, 那么可以先创建一个虚拟代理对象, 当程序实际需要使用此对象时, 才真正地实例化它, 这样就缩短了程序的启动时间, 即所谓的延时加载.

保护代理(protect proxy): 控制对一个对象的访问, 可以给不同的用户提供不同级别的使用权限. 例如我们可以在代理中检查用户的权限, 当权限不足时, 禁止用户调用此对象的方法.

缓存代理(cache proxy): 对实际对象的调用结果进行缓存. 例如一些复杂的操作, 如数据库读取等, 可以通过缓存代理将结果存储起来, 下次再调用时, 直接返回缓存的结果.

图片代理(image proxy): 当用户需要加载大型图片时, 可以通过代理对象的方法来进行处理, 即在代理对象的方法中, 先使用一个线程向客户端浏览器加载一个小图片, 然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端.

关于静态代理

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

定义一个加载图片的接口

实现实际操作对象(LoadFromDisk, LoadFromNet, LoadFromDB)

实现代理对象

根据上面的流程, 我们实现的代码如下:
接口:

/**
 * @author xiongyongshun
 * @version 1.0
 * @created 16/10/7
 */
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);
    }
}

使用:

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 的动态代理呢? 其实很简单, 顾名思义, 所谓动态代理就是 动态地创建代理并且动态地处理所代理对象的方法调用.
在 Java 的动态代理中, 涉及两个重要的类或接口:

Proxy

InvocationHandler

关于 Proxy 类

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

public static Object newProxyInstance(ClassLoader loader,
                                  Class[] interfaces,
                                  InvocationHandler h)
    throws IllegalArgumentException

此静态方法需要三个参数:

loader: 即类加载器, 指定由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces: 一个Interface对象的数组, 表示的是代理对象所需要实现的接口.

h: 即 InvocationHandler 的实现对象. 当调用代理对象的接口时, 实际上会 通过 InvocationHandler.invkoe 将调用转发给实际的对象.

这个静态类会返回一个代理对象, 在程序中可以可通过这个代理对象来对实际对象进行操作.

关于 InvocationHandler 接口

我们在前面提到过, 在调用 Proxy.newProxyInstance 方法时, 需要传递一个 InvocationHandler 接口的实现对象, 那么这个 InvocationHandler 接口有什么用呢?
实际上, 在 Java 动态代理中, 我们都必须要实现这个接口, 它是沟通了代理对象和实际对象的桥梁, 即:

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.invoke 方法中, 在 invoke 方法中调用实际的对象的对应方法.

InvocationHandler.invoke 方法的声明如下:

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

这个方法接收三个参数:

proxy: 指代理对象, 即 Proxy.newProxyInstance 所返回的对象(注意, proxy 并不是实际的被代理对象)

method: 我们所要调用真实对象的方法的 Method 对象

args: 调用真实对象某个方法时接受的参数

invoke 方法的返回值是调用的真实对象的对应方法的返回值.

动态代理例子

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

实现 InvocationHandler 接口, 并在 invoke 中调用真实对象的对应方法.

通过 Proxy.newProxyInstance 静态方法获取一个代理对象.

我们还是以在静态代理中展示的加载图片的例子为例, 首先加载图片的接口如下:
接口:

/**
 * @author xiongyongshun
 * @version 1.0
 * @created 16/10/7
 */
public interface LoadImage {
    Image loadImage(String name);
}

接下来我们需要实现 InvocationHandler 接口:

/**
 * @author xiongyongshun
 * @version 1.0
 * @created 16/10/7
 */
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 {
        System.out.println("Proxy class: " + proxy.getClass() + ", method: " + method + ", args: " + args);
        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");
    }
}
为什么需要使用动态代理

看了 静态代理动态代理, 有的朋友就会有疑惑了, 明明使用静态代理就可以完成的功能, 为什么还需要使用动态代理呢?
我认为相比静态代理, 动态代理有两点优点:

动态代理具有更强的灵活性, 因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象, 我们可以把这种指定延迟到程序运行时由JVM来实现.

动态代理更为统一与简洁.

为什么这么说呢? 我们还是以图片加载的例子说明吧. 现在我们假设 LoadImage 接口需要提供更多的方法, 并且我们希望每个方法调用都记录 Log. 因此 LoadImage 接口更改如下:

public interface LoadImage {
    // 加载图片
    Image loadImage(String name);
    // 加载图片, 并翻转图片
    Image loadAndRotateImage(String name);
    // 获取图片的缩略图
    Image loadSmallImage(String name);
}

我们添加了两个新的方法: loadAndRotateImage 和 loadSmallImage.
那么在静态代理的方法下, 我们怎么实现所需要的功能呢? 下面是具体的代码:

public class LoadImageProxy implements LoadImage {
    private LoadImage loadImageReal;

    public LoadImageProxy(LoadImage loadImageReal) {
        this.loadImageReal = loadImageReal;
    }

    @Override
    public Image loadImage(String name) {
        System.out.println("Call method: loadImage, file name: " + name);
        return loadImageReal.loadImage(name);
    }

    @Override
    public Image loadAndRotateImage(String name) {
        System.out.println("Call method: loadAndRotateImage, file name: " + name);
        return loadImageReal.loadImage(name);
    }

    @Override
    public Image loadSmallImage(String name) {
        System.out.println("Call method: loadSmallImage, file name: " + name);
        return loadImageReal.loadImage(name);
    }
}

上面代码例子中, 我们分别实现了 loadImage, loadAndRotateImage 和 loadSmallImage 代理方法, 并且为每个方法都添加了 log.
作为对比, 我们来看一下使用静态代理时的代码实现吧:

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 {
        System.out.println("Call method: loadImage, file name: " + args[0]);
        return method.invoke(proxied, args);
    }
}

我们看到, 在使用动态代理时, 我们除了添加一行 log 输出外, 没有进行任何的更改, 而在静态代理中, 我们需要分别实现每个代理方法, 并且在每个方法中添加日志输出. 可以想象, 当我们的接口方法比较多时, 使用静态代理就会造成了大量的代码修改, 并且在将来我们需要去除方法调用的 log 时, 静态代理的方式就十分不便了, 而对于动态代理而言, 仅仅需要修改一两行代码而已.

本文由 yongshun 发表于个人博客, 采用 署名-相同方式共享 3.0 中国大陆许可协议.
Email: yongshun1228@gmail .com
本文标题为: Java 动态代理(Dynamic proxy) 小结
本文链接为: https://segmentfault.com/a/1190000007089902

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66116.html

相关文章

  • Spring AOP的实现机制

    摘要:本文主要介绍的两种代理实现机制,动态代理和动态代理。直接使用首先定义需要切入的接口和实现。我实现了一个工厂类来获取代理对象代理具体使用输出结果动态代理我们再新建一个来,这次不实现任何接口。 AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP实现的关键在...

    dendoink 评论0 收藏0
  • 深入理解代理模式

    摘要:代理模式代理类中创建一个真实对象的实例模式的核心装饰者强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。 代理模式 在详细了解代理模式之前,可能对于像小秋一样的小白,只知道一些很浅显的概念,或者就知道远程代理啊,静态代理啊,动态代理啊,这些看似可以望文生义的专业名词,但是如果我告诉你代理模式贯穿了我们生活的方方面面,就比如你现在刷着公众号的时候,实际上就用了远程代理模...

    testHs 评论0 收藏0
  • Java的三种代理模式

    Java的三种代理模式 参考:http://www.cnblogs.com/cenyu/...Java核心技术原书第九版6.5节 为什么使用代理   我们在写一个功能函数时,经常需要在其中写入与功能不是直接相关但很有必要的代 码,如日志记录,信息发送,安全和事务支持等,这些枝节性代码虽然是必要的,但它会带来以下麻烦: 枝节性代码游离在功能性代码之外,它不是函数的目的,这是对OO是一种破坏 枝节性...

    Rango 评论0 收藏0
  • Java反射-动态代理

    摘要:动态代理有多种不同的用途,例如,数据库连接和事务管理用于单元测试的动态模拟对象其他类似的方法拦截。调用序列和下面的流程类似单元测试动态对象模拟利用动态代理实现单元测试的动态存根代理和代理。框架把包装成动态代理。 使用反射可以在运行时动态实现接口。这可以使用类java.lang.reflect.Proxy。这个类的名称是我将这些动态接口实现称之为动态代理的原因。动态代理有多种不同的用途,...

    Acceml 评论0 收藏0
  • 人人都会设计模式:代理模式--Proxy

    摘要:话说谁还干类似的事,就在文章末尾点个赞代销店等其实就是现在的商店,以前小的时候听家乡人叫代销店,也是一种代理模式。可以说是系统中最重要的架构之一。 showImg(https://segmentfault.com/img/remote/1460000012278678?w=1240&h=469); PS:转载请注明出处作者: TigerChain地址: http://www.jians...

    tuniutech 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<