代理模式及实现探究

前言

使用过Spring并经常Debug或者熟悉Spring实现原理的开发应该知道动态代理Spring框架实现的一大重要技术工具。

先看下图:

这里写图片描述

使用Spring依赖注入的bean通常都能看到CGLIB的标识。而我们自定义的类对象查看到对象信息基本和类定义无差。

究其原由是因为Spring依赖注入的bean并非原始的类对象,而是使用CGLIB的代理对象。

借由此本文旨在对代理模式及实现方式一探究竟。


代理模式介绍

概述

因为某个对象消耗太多资源,而且你的代码并不是每个逻辑路径都需要此对象, 你曾有过延迟创建对象的想法吗?

你有想过限制访问某个对象,也就是说,提供一组方法给普通用户,特别方法给管理员用户?

以上两种需求都非常类似,并且都需要解决一个更大的问题:
如何提供一致的接口给某个对象让它可以改变其内部功能,或者是从来不存在的功能?

可以通过引入一个新的对象,来实现对真实对象的操作或者将新的对象作为真实对象的一个替身。即代理对象。它可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。

用书面术语来描述:

  • 代理模式是常用的java设计模式。
  • 它的特征是代理类与委托类有同样的接口。
  • 代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
  • 代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

分类

按照使用场景:

  • 远程代理(Remote Proxy): 为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)
  • 虚拟代理(Virtual Proxy): 根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • 保护代理(Protection Proxy): 控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
  • 智能指引(Smart Reference): 取代了简单的指针,它在访问对象时执行一些附加操作。
  • Copy-on-Write代理: 它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

按照实现方式:

  • 静态代理: 在程序运行前就已经存在代理类的字节码文件。代理类和委托类的关系在运行前就确定了
  • 动态代理: 动态代理类的源码是在程序运行期由JVM根据反射机制动态生成的。代理类和委托类的关系是在程序运行时确定的

UML图示

这里写图片描述


跟着Demo深入探究

接下来我们自己动手用demo来实践一下代理模式的实现,并比较不同实现方式的区别。

公共接口及类定义:

public interface TestService {

    /** 打印入参字符串 */
    void saySomething(String str);

    /** 返回自增+1 */
    int countInt(int num);
}
public class TestServiceImpl implements TestService {
    @Override
    public void saySomething(String str) {
        System.out.println(str);
    }

    public int countInt(int num) {
        return (num++);
    }
}

静态代理实现demo

我们先看静态代理如何完成上述接口实现的代理:


public class ProxySubject implements TestService {

    // 代理类持有一个委托类的对象引用
    private TestService testService;

    public ProxySubject(TestService testService) {
        this.testService = testService;
    }

    /**
     * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间
     * 
     * @param taskName
     */
    @Override
    public void saySomething(String something) {

        Date startDate = new Date();
        System.out.println("开始调用目标类时时间点:" + startDate);

        // 将请求分派给委托类处理
        testService.saySomething(something);

        Date endDate = new Date();
        System.out.println("结束调用目标类时时间点:" + endDate);
    }

    @Override
    public int countInt(int num) {
        return testService.countInt(num);
    }
}

public class Client {
    public static void main(String[] args) {
        TestService proxy = new ProxySubject(new TestServiceImpl());
        proxy.saySomething("Hello buddy");
    }
}

下面是上述代理执行结果:

开始调用目标类时时间点:Mon Oct 17 11:01:04 CST 2016
Hello buddy
结束调用目标类时时间点:Mon Oct 17 11:01:04 CST 2016

静态代理实现总结:

上述例子,静态代理类做的事情即是

  • 在真实调用目标接口实现时打印接口调用的请求时间
  • 调用目标接口
  • 目标接口调用结束后打印请求完成时间

我们发现:

  • 使用了静态代理。我们不需要入侵真实的目标类即可在目标对象调用时封装一套额外的逻辑。当然这是代理模式的有点,不局限于静态代理
  • 为了实现接口的代理,我们必须要定义一个代理类实现同一个接口,在实现中显示的调用目标接口来完成代理的实现
    • 基于这点,这也反应了静态代理实现的一大缺点:
      • 一个代理对象服务于同一类的对象。业务类的所有方法都需要进行代理;业务类每新增一个接口,所有的代理类也要实现这个接口,增加代码维护的复杂度

JDK动态代理Demo

上述静态代理的缺陷,而动态代理的特性正好可以解决。

而动态代理也有很多种实现技术手段。这一节讲讲java提供的原生动态代理:


/**
 * JDK动态代理类
 */
public class ProxyHandler {
    public static Object getPoxyObject(final Object c) {
        return Proxy.newProxyInstance(c.getClass().getClassLoader(), c.getClass().getInterfaces(), // JDK实现动态代理,但JDK实现必须需要接口
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object reObj = null;
                        reObj = method.invoke(c, args);
                        if (method.getName().equals("saySomething")) {
                            System.out.println("at [" + Calendar.getInstance().get(Calendar.HOUR) + ":"
                                    + Calendar.getInstance().get(Calendar.MINUTE) + ":"
                                    + Calendar.getInstance().get(Calendar.SECOND) + "]\n");
                        }
                        return reObj;
                    }
                });
    }
}
/**
 * 测试客户端
 */

public class JDKServiceMain {
    public static void main(String[] args) {

        TestService service = new TestServiceImpl();
        TestService poxyService = (TestService) ProxyHandler.getPoxyObject(service);

        System.out.println("\n\nexcute info:\n");
        poxyService.saySomething("Manager Zhou: Hello, GentleSong.");
        poxyService.saySomething("Manager Zhou: you are KXF's dream guy.");
        poxyService.saySomething("Manager Zhou: Are you willing to sacrifice for the happniess of KXF's buddy?");
        poxyService.saySomething("GentleSong: Yes, I am.");

    }
}

下面是上述代理执行结果:

excute info:

Manager Zhou: Hello, GentleSong.
at [11:35:10]

Manager Zhou: you are KXF's dream guy.
at [11:35:10]

Manager Zhou: Are you willing to sacrifice for the happniess of KXF's buddy?
at [11:35:10]

GentleSong: Yes, I am.
at [11:35:10]

JDK动态代理实现总结:

上述例子,动态代理类做的事情即是

  • 调用目标接口
  • 调用结束时打印请求调用完成时间

我们发现:

  • JDK的实现方式看,它可以实现一类接口的的代理。
    • 我们不需要每新增一个接口即新增一个代理类实现
    • 我们不需要接口定义新增或删减时同时要修改代理类
  • 但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理。

CGLIB动态代理Demo

JDK的动态代理是基于接口实现的。那没有接口定义的类实现如何代理嘞? CGLIB能够解决这个问题。


/**
 * CGLIB动态代理类
 */
public class ProxyHandler {
    public static Object getPoxyObject(Object c) {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(c.getClass());

        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable {

                proxy.invokeSuper(arg0, arg2);

                if (arg1.getName().equals("saySomething")) {
                    System.out.println("at [" + Calendar.getInstance().get(Calendar.HOUR) + ":"
                            + Calendar.getInstance().get(Calendar.MINUTE) + ":"
                            + Calendar.getInstance().get(Calendar.SECOND) + "]\n");
                }

                return null;
            }
        });

        return enhancer.create();
    }
}

public class ProxyHandler {
    public static Object getPoxyObject(Object c) {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(c.getClass());

        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable {

                proxy.invokeSuper(arg0, arg2);

                if (arg1.getName().equals("saySomething")) {
                    System.out.println("at [" + Calendar.getInstance().get(Calendar.HOUR) + ":"
                            + Calendar.getInstance().get(Calendar.MINUTE) + ":"
                            + Calendar.getInstance().get(Calendar.SECOND) + "]\n");
                }

                return null;
            }
        });

        return enhancer.create();
    }
}

CGLIB动态代理实现总结:

上述例子,动态代理类做的事情即是

  • 调用目标接口
  • 调用结束时打印请求调用完成时间

CGLIB实现原理:

  • CGLIB是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强(当然这运用到了java的又一特性:修改字节码)。由于使用的是继承的方式,如果类或者方法被声明为final,将无法使用CGLIB的动态代理。

几种代理实现方式的比较

上述几种代理实现方式实践之后,大家是否就粗暴的认为CGLIB动态代理的方式是最优项嘞?毕竟它解决了静态代理和JDK动态代理的缺陷。

这里我们不要这么快给出判断。同样实践得真知,下面我们用demo来测试一下几种代理实现方式的性能:

测试分为以下几个维度:

  • 在单例模式(仅创建一次代理类)下分别执行100万、500万次静态代理、JDK动态代理、CGLIB动态代理
  • 在多例模式(每次调用新创建代理类)下分别执行100万、500万次静态代理、JDK动态代理、CGLIB动态代理
  • 细化测试代理对象创建、代理类执行的时耗测试

PS:在单例测试和多例测试请勿按比例比较,毕竟多例测试中不断地创建及销毁对象也是时间花销。但是不同实现方式的多例测试还是有参考性的。

具体的代码示例已上传至GitHub,链接地址为:https://github.com/huangnanxi/proxyDemo

PS:
demo的例子中:cglib采用的是对类的代理的调用方式(setSuperClass,invokerSuper)

基于这样的前提:比较,我们得出以下结论:

  • 在单例模式下。JDK动态代理与CGLIB动态代理性能相差不大
  • 在多例模式下。JDK动态代理的性能远大于CGLIB动态代理
  • JDK1.7的JDK动态代理性能较于JDK1.6有明显的提升

深入背后的原因是:

  • JDK在创建代理(生成字节码时)效率远大于CGLIB
  • JDK动态代理执行与CGLIB动态代理(类代理)执行的效率相差无几

若cglib采用的是对接口的代理的调用方式(setInterface,invoker)(大家可以修改一些demo验证一下)

结论也同cglib类代理

开普勒鑫球-贾克斯收集、整理。

这里写图片描述

欢迎一起交流。转载请注明出处:开普勒鑫球

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值