代理模式(Proxy Pattern)

一、代理模式定义

给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
通俗的来讲代理模式就是我们生活中常见的中介。

二、 代理模式的结构

代理模式的主要角色如下:

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

其通用类图如下:


代理模式类图

三、代理模式优缺点

3.1 代理模式的主要优点有:
  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
3.1 其主要缺点是:
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

四、静态代理和动态代理

我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。

静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。

动态代理是在程序运行时通过反射机制动态创建的。现在有一个非常流行的名称叫做:面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。Spring AOP实现动态代理采用了两种方式,即JDK动态代理和CGLIB动态代理。

JDK动态代理是通过实现被代理类共同接口来完成代理,所以是基于接口的动态代理;CGLIB动态代理是通过继承并实现被代理类来完成代理,所以是基于继承的代理

五、静态代理实现

5.1 抽象主题类:Sleep
/**
 * 抽象主题角色
 */
public interface Sleep {
    void sleep();
}
5.2 真实主题类:SleepImpl
/**
 * 真实主题角色
 */
public class SleepImpl implements Sleep {
    @Override
    public void sleep() {
        System.out.println("熟睡中");
    }
}
5.3 代理类:SleepProxy
/**
 * 代理角色
 */
public class SleepProxy implements Sleep {
    private Sleep sleep;
    public SleepProxy(Sleep sleep) {
        this.sleep = sleep;
    }  
    @Override
    public void sleep() {   
        System.out.println("睡觉前要刷牙");
        sleep.sleep();
        System.out.println("睡醒后要吃早饭");
    }
}
5.4 客户端测试类
public class Client {
    public static void main(String[] args) {
        Sleep sleep = new SleepImpl();
        Sleep sleepProxy = new SleepProxy(sleep);
        sleepProxy.sleep();
    }
}

打印结果:

睡觉前要刷牙
熟睡中
睡醒后要吃早饭

六、动态代理实现(JDK)

静态代理有一个明显的缺点:一个主题类和一个代理类一一对应,所以我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。 在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建。

6.1 抽象主题类:Sleep、真实主题类:SleepImpl不变
/**
 * 抽象主题角色
 */
public interface Sleep {
    void sleep();
}

/**
 * 真实主题角色
 */
public class SleepImpl implements Sleep {
    @Override
    public void sleep() {
        System.out.println("熟睡中");
    }
}
6.2 新增动态处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 动态处理器
 */
public class DynamicProxyHandler implements InvocationHandler {   
    private Object obj ;
    public  DynamicProxyHandler(final Object obj) {       
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("睡觉前要刷牙");
        Object result = method.invoke(obj, args);
        System.out.println("睡醒后要吃早饭");
        return null;
    }
}
6.3 客户端测试类
import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        Sleep sleep = new SleepImpl();
        DynamicProxyHandler dph = new DynamicProxyHandler(sleep);
        Sleep sleepProxy = (Sleep) Proxy.newProxyInstance(sleep.getClass().getClassLoader(),
                sleep.getClass().getInterfaces(), dph);
        sleepProxy.sleep();
    }
}

打印结果:

睡觉前要刷牙
熟睡中
睡醒后要吃早饭

注意:Proxy.newProxyInstance()方法需要3个参数:类加载器(要进行代理的类)、被代理类实现的接口,动态处理器 。

JDK动态代理的一般步骤:
1、创建动态处理器实现InvocationHandler接口,实现invoke()方法
2、创建被代理的类及接口
3、调用Proxy的静态方法,创建代理类
4、通过代理类调用方法

拓展:查看JDK动态生成的代理类源码

用ProxyGenerator.generateProxyClass生成代理类,并生成.class文件写入本地磁盘,我们再通过javap -c 命令查看JDK动态生成的代理类源码

import java.io.File;
import java.io.FileOutputStream;
import sun.misc.ProxyGenerator;

public class ShowDynamicProxTest {

    public static void main(String[] args) {
        Sleep sleep = new SleepImpl();
        byte[] bts = ProxyGenerator.generateProxyClass("$GameProxy", sleep.getClass().getInterfaces());
        try {
            FileOutputStream fos = new FileOutputStream(new File("/Users/AC/temp/$DynamicSleepProxy.class"));
            fos.write(bts);
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

当你尝试拷贝我这段代码在你本地运行时,你会发现你无法导入import sun.misc.ProxyGenerator;你可以这样操作来解决:

1、打开项目属性》Java Build Path》选择JRE System Library》remove;
2、重新添加JRE system library:点击Add library》Next》选择相应的JDK版本(建议与刚开始的一样的版本)》finish。
3、再来尝试导入sun.misc.ProxyGenerator,发现已经可以导入了。

更多方式可参考:导入某些特殊Jar包中的类

现在我们用javap -c来查看一下我们生成在本地的$DynamicSleepProxy.class源码(当然也可以用其他方式来查看源码,如安装插件)

AC:temp AC$ javap -c \$DynamicSleepProxy.class 
public final class $SleepProxy extends java.lang.reflect.Proxy implements com.Sleep {
  public $SleepProxy(java.lang.reflect.InvocationHandler) throws ;
    Code:
       0: aload_0
       1: aload_1
       2: invokespecial #8                  // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
       5: return

  public final boolean equals(java.lang.Object) throws ;
    Code:
       0: aload_0
       1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
       4: aload_0
       5: getstatic     #20                 // Field m1:Ljava/lang/reflect/Method;
       8: iconst_1
       9: anewarray     #22                 // class java/lang/Object
      12: dup
      13: iconst_0
      14: aload_1
      15: aastore
      16: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
      21: checkcast     #30                 // class java/lang/Boolean
      24: invokevirtual #34                 // Method java/lang/Boolean.booleanValue:()Z
      27: ireturn
      28: athrow
      29: astore_2
      30: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
      33: dup
      34: aload_2
      35: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
      38: athrow
    Exception table:
       from    to  target type
           0    28    28   Class java/lang/Error
           0    28    28   Class java/lang/RuntimeException
           0    28    29   Class java/lang/Throwable

  public final java.lang.String toString() throws ;
    Code:
       0: aload_0
       1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
       4: aload_0
       5: getstatic     #50                 // Field m2:Ljava/lang/reflect/Method;
       8: aconst_null
       9: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
      14: checkcast     #52                 // class java/lang/String
      17: areturn
      18: athrow
      19: astore_1
      20: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
      23: dup
      24: aload_1
      25: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
      28: athrow
    Exception table:
       from    to  target type
           0    18    18   Class java/lang/Error
           0    18    18   Class java/lang/RuntimeException
           0    18    19   Class java/lang/Throwable

  public final void sleep() throws ;
    Code:
       0: aload_0
       1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
       4: aload_0
       5: getstatic     #57                 // Field m3:Ljava/lang/reflect/Method;
       8: aconst_null
       9: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
      14: pop
      15: return
      16: athrow
      17: astore_1
      18: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
      21: dup
      22: aload_1
      23: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
      26: athrow
    Exception table:
       from    to  target type
           0    16    16   Class java/lang/Error
           0    16    16   Class java/lang/RuntimeException
           0    16    17   Class java/lang/Throwable

  public final int hashCode() throws ;
    Code:
       0: aload_0
       1: getfield      #16                 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
       4: aload_0
       5: getstatic     #62                 // Field m0:Ljava/lang/reflect/Method;
       8: aconst_null
       9: invokeinterface #28,  4           // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
      14: checkcast     #64                 // class java/lang/Integer
      17: invokevirtual #67                 // Method java/lang/Integer.intValue:()I
      20: ireturn
      21: athrow
      22: astore_1
      23: new           #42                 // class java/lang/reflect/UndeclaredThrowableException
      26: dup
      27: aload_1
      28: invokespecial #45                 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
      31: athrow
    Exception table:
       from    to  target type
           0    21    21   Class java/lang/Error
           0    21    21   Class java/lang/RuntimeException
           0    21    22   Class java/lang/Throwable

  static {} throws ;
    Code:
       0: ldc           #70                 // String java.lang.Object
       2: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
       5: ldc           #77                 // String equals
       7: iconst_1
       8: anewarray     #72                 // class java/lang/Class
      11: dup
      12: iconst_0
      13: ldc           #70                 // String java.lang.Object
      15: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
      18: aastore
      19: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
      22: putstatic     #20                 // Field m1:Ljava/lang/reflect/Method;
      25: ldc           #70                 // String java.lang.Object
      27: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
      30: ldc           #82                 // String toString
      32: iconst_0
      33: anewarray     #72                 // class java/lang/Class
      36: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
      39: putstatic     #50                 // Field m2:Ljava/lang/reflect/Method;
      42: ldc           #84                 // String com.Sleep
      44: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
      47: ldc           #85                 // String sleep
      49: iconst_0
      50: anewarray     #72                 // class java/lang/Class
      53: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
      56: putstatic     #57                 // Field m3:Ljava/lang/reflect/Method;
      59: ldc           #70                 // String java.lang.Object
      61: invokestatic  #76                 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
      64: ldc           #86                 // String hashCode
      66: iconst_0
      67: anewarray     #72                 // class java/lang/Class
      70: invokevirtual #81                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
      73: putstatic     #62                 // Field m0:Ljava/lang/reflect/Method;
      76: return
      77: astore_1
      78: new           #90                 // class java/lang/NoSuchMethodError
      81: dup
      82: aload_1
      83: invokevirtual #93                 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
      86: invokespecial #96                 // Method java/lang/NoSuchMethodError."<init>":(Ljava/lang/String;)V
      89: athrow
      90: astore_1
      91: new           #100                // class java/lang/NoClassDefFoundError
      94: dup
      95: aload_1
      96: invokevirtual #93                 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
      99: invokespecial #101                // Method java/lang/NoClassDefFoundError."<init>":(Ljava/lang/String;)V
     102: athrow
    Exception table:
       from    to  target type
           0    77    77   Class java/lang/NoSuchMethodException
           0    77    90   Class java/lang/ClassNotFoundException
}

通过看源码我们发现,JDK动态生成的代理类SleepProxy继承了java.lang.reflect.Proxy类,同时实现了我们的Sleep类。

问题1:为什么JDK动态代理只能代理实现了接口的类?

因为JDK动态代理生成的代理类需要去继承Proxy类,而Java是单继承的,因此它只能去实现被代理类的接口(实现的接口)

问题2:为什么JDK动态代理生成的代理类要去继承Proxy类?

不仔细看的话,生成的代理类会给人一种继承Proxy类没有用的感觉,因为生成的代理类并没有用什么Proxy类的东西,很有迷惑性,我也是将生成的代理类代码亲自拿来试了一下才发现它的用处。

事实上,在代理类$Proxy中有许多处方法的调用,都是通过使用super关键字去拿父类也就是proxy类中定义的InvocationHandler实例然后完成调用的。Super关键字正是在调用父类Prioxy类的方法。这就是用到proxy类的地方。

在代理类Proxy中调用父类的方法都是使用了InvocationHandler类的实例,在代理类$Proxy中通过构造方法传入进来,而我们应该还记得,在Proxy的newProxyInstance方法中的第三个参数,正是InvocationHandler的一个实例。

因此这里调用方法时,自然就是使用这个实例去调用invoke方法,同时传入对应的方法参数。这下,所有的东西都串联起来了,问题三的答案事实上也已经有了。

重点:

JDK生成的代理类本身就继承了Proxy类,Java只允许单继承,所以JDK的动态代理不能完成继承式动态代理(只能完成接口的动态代理,不能实现类的动态代理),但是我们可以用CGLIB的方式实现继承式的动态代理

七、动态代理实现(CGLIB)

7.1 具体主题(不需要实现接口)
/**
 * 具体主题
 */
public class Sleep {
    public void sleep() {
        System.out.println("熟睡中");
    }
}
7.2 CGLibProxy
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CGLibProxy implements MethodInterceptor {
     
    private Object obj;
     
    //创建代理类
    public Object getInstance(Object obj) {
        this.obj = obj;//主题对象
        Enhancer enhancer = new Enhancer();//创建加强器,用来创建动态代理类
        enhancer.setSuperclass(this.obj.getClass());
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
        enhancer.setCallback(this);
        //返回代理类
        return enhancer.create();
    }
     
    //回调方法实现
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
     throws Throwable {
        System.out.println("睡觉前要刷牙");
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("睡醒后要吃早饭");
        return result;
    }
 }
7.3 测试类
public class Client {
    public static void main(String[] args) {
        Sleep sleep = new Sleep();
        CGLibProxy proxy = new CGLibProxy();
        Sleep sleepProxy = (Sleep) proxy.getInstance(sleep);
        sleepProxy.sleep();
    }
}

打印结果

睡觉前要刷牙
熟睡中
睡醒后要吃早饭

注意:使用CGLIB需要下载两个jar包:cglib.jar和asm.jar
CGLib » 2.2.2 jar下载
使用CGlib出现java.lang.NoClassDefFoundError: org/objectweb/asm/Type异常
asm jar下载

八、总结

静态代理虽然能完成代理功能,但我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

JDK动态代理很美很强大,但由于Java的单继承,只能实现接口的动态代理,不能实现类的动态代理。

CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

参考:

最后给大家送波福利

阿里云折扣快速入口

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

推荐阅读更多精彩内容