java 代理模式与 JDK 代理

本贴最后更新于 2733 天前,其中的信息可能已经斗转星移

前言

代理模式是很常用的设计模式之一,一般可分为静态代理和动态代理两类。java 利用反射也对动态代理提供了支持。今天我们就来学习学习。

1. 定义

给某一个对象提供一个代理,并由代理对象控制对原对象的引用,称为代理模式。它是一种对象结构型模式。

即可理解为,某个对象实例(记为 Subject)不方便直接引用,我们就提供一个代理实例(记为 Proxy),让这个代理实例去调用实例对象。我们直接与 Proxy 打交道,由 Proxy 负责与 Subject 沟通。

这样说来,Proxy 相当于一个中间人的角色,负责我们实际对象的沟通。

2. 情景示例

我们通过代码来描述这种模式。

假设一种情景,图片的加载工作。大图片加载很耗时,我们希望在大图片加载过程中先做一些操作(比如显示张小图片、或给个文字提示),等大图片加载完毕后再显示大图片。

有一点需要注意,在显示大图片前做的操作不确定,这样就不能写死在大图片加载中。让我们考虑下代理模式。

需要有一个共同的接口类

是的。代理对象(Proxy)对外要表现实际对象(Subject)的功能,所以它们应该有一个共同的接口。

public interface ImageHandler {
    
    void loadImage();
}

下面是真正的图片加载类

public class ImageHandlerImpl implements ImageHandler {
    @Override
    public void loadImage() {
        try {
            //用休眠2秒表示图片加载过程
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("图片加载完成。");
    }
}

该设计我们的代理类了。我们知道,代理类是要能执行我们的真实对象方法的。怎样执行?最简单的方式当然是把真实对象的实例传给代理。

public class ImageHandlerProxy implements ImageHandler{
    private ImageHandler imageHandler;

    //将真实对象通过构造器传过来
    public ImageHandlerProxy(ImageHandler imageHandler) {
        this.imageHandler = imageHandler;
    }

    @Override
    public void loadImage() {
        //代理做一些预处理
        System.out.println("请等待,正在加载图片");
//        System.out.println("加载小图片");
        
        //调用真实对象方法
        imageHandler.loadImage();
        
        //可以做一些收尾工作
    }
}

这样代理就完成。测试一下

    @Test
    public void test(){
        ImageHandler handler = new ImageHandlerImpl();
        ImageHandlerProxy proxy = new ImageHandlerProxy(handler);

        proxy.loadImage();

    }

结果先出现提示信息,过一会后图片加载完毕

请等待,正在加载图片
图片加载完成。

3. 动态代理

在了解动态代理前,我们先思考一下,上面的例子有什么问题?

  1. 动态代理只是实现对一种对象的代理,如上例 ImageHandler。这样导致复用性很差,比如我想对视频也实现这样的代理,还要再写一个视频代理类。
  2. 代理方法也写死在接口中(如上例的 loadImage() 方法),这样我再代理一个方法就要在代理对象再实现一遍。

当然,这些讨论是建立在你有这些需求的基础上。比如你就只需要一个图片加载的代理,静态代理也就足够了。

我们需要一种方法来解决上面的问题。上面例子中,代理类是事先写好的,运行时早已确定。而我们希望的是,能够在运行时动态生成某个类,这样就不必为每个真正需代理的对象都写一个代理类了。

JDK 直接实现了这种需求。我们只需要做到:

  1. 写一个动态代理类实现 InvocationHandler 接口。
  2. 使用 Proxy 类生成代理对象实例。

下面通过代码演示一下

上例中的 ImageHandlerImageHandlerImpl 仍保留。

public class MyDynamicProxy implements InvocationHandler {

    private Object subject;

    //通过构造函数传入实际对象实例
    public MyDynamicProxy(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置工作");
        Object result = method.invoke(subject,args);
        System.out.println("收尾工作");
        return result;
    }
}

MyDynamicProxy 与前面的静态代理模式中 ImageHandlerProxy 相似,都是传入真正对象,最后调用真实对象完成。不同的是,动态代理不耦合某个接口。

生成代理对象与测试

    @Test
    public void testDynamic(){

        //真实对象
        ImageHandler realObject = new ImageHandlerImpl();

        //代理对象的处理器
        InvocationHandler  handler = new MyDynamicProxy(realObject);

        //生成代理对象
        ImageHandler imageProxy = (ImageHandler) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                new Class[]{ImageHandler.class},handler);

        imageProxy.loadImage();
    }

这就是 JDK 的动态代理实例。其中最关键的莫过 newProxyInstance 方法

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

参数分别为类加载器、接口数组及实现了 InvocationHandler 接口的对象实例。newProxyInstance() 可根据这些信息创建代理对象实例。从而实现动态代理。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • wthfeng
    作者

    感谢指正。
    文章里面的确写错了。那部分应该是 ImageHandlerImpl.class.getInterfaces(),表明传入的是其共同接口类的数组,等同于 new Class[]{ImageHandler.class}。经考虑后者写法更明确一些。已修正。

  • 其他回帖
  • clearbug

    感觉动态代理那里有玄机看不懂啊,,,可作者你还就贴了几行简单的实现代码。。。哎,菜鸟没能看懂

  • SisyphusNee

    你好,在 testDynamic()newProxyInstance 语句第二个参数不知为何运行出错:

    ImageHandler imageProxy = (ImageHandler) Proxy.newProxyInstance(handler.getClass().getClassLoader(),handler.getClass().getInterfaces(),handler);
    
    

    报错信息为:com.sun.proxy.$Proxy0 cannot be cast to XX

    改成这样以后能够跑通:

    ImageHandler imageProxy = (ImageHandler) Proxy.newProxyInstance(handler.getClass().getClassLoader(),new Class[]{ImageHandler.class},handler);
    
    
    1 回复