文章

设计模式 - 代理模式

代理模式

代理模式是一种设计模式,提供了对目标对象的访问方式,通过代理对象调用目标对象的方法,可以在实现目标对象的基础上,增强额外的功能,即扩展了目标对象。 在使用过程中调用者不直接调用目标对象,而是通过代理对象访问目标对象。

首先讲一个费玉清嘿嘿嘿的段子(大家应该都知道)来引入代理模式
费玉清想减肥于是就去了一家减肥中心,前台小姐说:有五百的有一千的还有两千的,功效看价钱,你要哪种?他首先选了五百的想试试,前台小姐说,拿着这个钥匙,去第一扇门,男人拿着钥匙去了第一扇门。板凳上坐着一个穿着比基尼的美女,美女说:你追我,如果你追到我,我就让你嘿嘿嘿~他就追啊追,终于追到了,然后美女就让他嘿嘿嘿了。
费玉清回到家一看,果然瘦了五斤。过了几天,他又去那家店,前台小姐准备问,费玉清说,别问了,我是老顾客,接着选了一千的,前台小姐给了他一把钥匙,让他打开第二扇门。一个美女坐在板凳上,这次什么都没穿,说:你追我,如果你追上我,我就让你嘿嘿嘿。费玉清没有追上美女,可他们还是嘿嘿嘿了。
回去一看,又瘦了五斤。过了几天,他又去了那家店,点了两千的,前台小姐给了他一把钥匙,让他打开第三扇门。第三扇门里坐着一个母猩猩,说:我追你,如果我追到你,你就让我嘿嘿嘿。
嘿嘿嘿

在这个段子里面,我们可以把前台小姐当成代理对象,而比基尼辣妹、什么都没穿的辣妹和母猩猩就是被代理对象,前台小姐给钥匙的动作就是确定目标对象,前台小姐也知道房间里面的辣妹都会做哪些事情,所以前台小姐可以作为房间里面辣妹的代理对象。

三种代理模式

代理模式的实现主要有三种:

  1. 静态代理
  2. 动态代理(JDK 代理)
  3. Cglib代理

静态代理

代理对象和目标对象实现相同的接口或者继承相同的父类,代理对象是目标对象的扩展,持有目标对象的引用,会调用目标对象的方法。

示例

把嘿嘿嘿段子用静态代理模式翻译成代码

  1. 首先订单一个接口 IWomen 表示减肥中心里面的女性,她们会 run 和 heiheihei 同时她们也有自己的名字
    1
    2
    3
    4
    5
    6
    7
    8
    
     public interface IWomen {
    
         void run();
    
         void heiheihei();
    
         String getName();
     }
    
  2. 定义 500、1000、2000 的目标对象实现 IWomen

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    
     /**
      * 比基尼辣妹 500
     */
     public class BikiniWoman implements IWomen {
         @Override
         public void run() {
             System.out.println("比基尼辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿");
         }
    
         @Override
         public void heiheihei() {
             System.out.println("比基尼辣妹开始嘿嘿嘿");
         }
    
         @Override
         public String getName() {
             return "比基尼辣妹";
         }
     }
    
     /**
      * 没穿衣服的辣妹 1000
     */
     public class NakedWoman implements IWomen {
         @Override
         public void run() {
             System.out.println("没穿衣服的辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿");
         }
    
         @Override
         public void heiheihei() {
             System.out.println("没穿衣服的辣妹开始嘿嘿嘿");
         }
    
         @Override
         public String getName() {
             return "没穿衣服的辣妹";
         }
     }
    
     /**
      * Gorilla 2000
     */
     public class GorillaWoman implements IWomen {
         @Override
         public void run() {
             System.out.println("Gorilla 说:我追你,如果我追到你,你就让我嘿嘿嘿");
         }
    
         @Override
         public void heiheihei() {
             System.out.println("费玉清被开始嘿嘿嘿");
         }
    
         @Override
         public String getName() {
             return "Gorilla";
         }
     }
    
  3. 定义一个代理对象——前台小姐

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
     /**
      * 减肥中心前台接待小姐
     */
     public class Receptionist implements IWomen {
    
         private IWomen iWomen;
    
         public Receptionist() {
             // 默认是 500 比基尼辣妹的代理
             this.iWomen = new BikiniWoman();
         }
    
         public Receptionist(IWomen iWomen) {
             this.iWomen = iWomen;
         }
    
         public void setWomen(IWomen iWomen) {
             this.iWomen = iWomen;
         }
    
         @Override
         public void run() {
             System.out.println("前台小姐叫 " + iWomen.getName() + " 起跑了");
             iWomen.run();
             if ("Gorilla".equals(iWomen.getName())) {
                 System.out.println("前台小姐看到 " + iWomen.getName() + " 追到费玉清了");
             } else {
                 System.out.println("前台小姐看到 " + iWomen.getName() + " 被追到了");
             }
         }
    
         @Override
         public void heiheihei() {
             System.out.println("前台小姐安排 " + iWomen.getName() + " 开始嘿嘿嘿了");
             iWomen.heiheihei();
         }
    
         @Override
         public String getName() {
             return "前台小姐";
         }
     }
    
  4. 费玉清登场

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
     public class FeiYuQing {
    
         public static void main(String[] args) {
             // 费玉清来到减肥中心找到前台接待小姐
             Receptionist receptionist = new Receptionist();
             // 询问价格后,先选个 500 的试试
             IWomen iWomen500 = new BikiniWoman();
             receptionist.setWomen(iWomen500);
             receptionist.run();
             receptionist.heiheihei();
             System.out.println();
             // 费玉清回家之后一看,瘦了5斤
             // 过了几天又一次来到了减肥中心,这次来个 1000 的
             IWomen iWomen1000 = new NakedWoman();
             receptionist.setWomen(iWomen1000);
             receptionist.run();
             receptionist.heiheihei();
             System.out.println();
             // 这次又瘦了5斤
             // 效果这么明显,费玉清决定加大力度,这次直接上 2000
             IWomen iWomen2000 = new GorillaWoman();
             receptionist.setWomen(iWomen2000);
             receptionist.run();
             receptionist.heiheihei();
         }
     }
    

代码运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
前台小姐叫 比基尼辣妹 起跑了
比基尼辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
前台小姐看到 比基尼辣妹 被追到了
前台小姐安排 比基尼辣妹 开始嘿嘿嘿了
比基尼辣妹开始嘿嘿嘿

前台小姐叫 没穿衣服的辣妹 起跑了
没穿衣服的辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
前台小姐看到 没穿衣服的辣妹 被追到了
前台小姐安排 没穿衣服的辣妹 开始嘿嘿嘿了
没穿衣服的辣妹开始嘿嘿嘿

前台小姐叫 Gorilla 起跑了
Gorilla 说:我追你,如果我追到你,你就让我嘿嘿嘿
前台小姐看到 Gorilla 追到费玉清了
前台小姐安排 Gorilla 开始嘿嘿嘿了
费玉清被开始嘿嘿嘿

在上面的代码中,费玉清每次到减肥中心都是先跟前台小姐接触,通过前台小姐安排辣妹进行“减肥活动”,而前台小姐根据价格为费玉清安排不同的辣妹,并且可以监控安排减肥过程,在这个过程中干活的是辣妹(被代理类),前台小姐(代理类)主要是接活安排工作,那怎么知道被代理类能不能干活呢?知根知底就行,大家都是 IWomen,你能做啥,我能做啥都很清楚。

小结

  • 静态代理模式主要使用了 Java 的多态
  • 可以做到对目标对象进行功能扩展。
  • 代理对象需要与目标对象实现一样的接口,所以会产生很多代理类,同时,一旦接口增加方法,目标对象与代理对象都要维护。

动态代理(JDK 代理)

动态代理可以用来解决静态代理存在的缺点:产生大量代理类并且接口改动时需要同时维护目标对象和代理对象。
动态代理过程中我们不需要通过实现同个接口来手动创建代理类,代理对象的生成是利用 JDK 的 API 动态的在内存中创建的(需要我们指定创建代理对象/目标对象实现的接口的类型)。
动态代理使用到的类是 java.lang.reflect.Proxy,使用到类中的方法是 newProxyInstance
Proxy.newProxyInstance() 接收三个参数:

  • ClassLoader loader 用于定义代理类的类加载器
  • Class<?>[] interfaces 要实现的代理类的接口列表
  • InvocationHandler h 指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法(是一个接口,需要编写自己的实现类)

示例

把嘿嘿嘿段子用动态代理模式翻译成代码
IWomen 接口以及实现 IWomen 的各个辣妹实现同静态代理一样,这里就不赘述了,直接从 InvocationHandler开始

  1. InvocationHandler 实现类,通过 invoke 实现了对目标对象方法的扩展,把静态代理中代理对象的增强功能实现放在这里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
     public class WomenHandler implements InvocationHandler {
    
         private Object object;
    
         public WomenHandler(Object object) {
             this.object = object;
         }
    
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             String methodName = method.getName();
             String simpleName = object.getClass().getSimpleName();
             Object returnValue;
             if ("run".equals(methodName)) {
                 System.out.println("动态生成的前台小姐叫 " + simpleName + " 起跑了");
                 returnValue = method.invoke(object, args);
                 if ("GorillaWoman".equals(simpleName)) {
                     System.out.println("动态生成的前台小姐看到 " + simpleName + " 追到费玉清了");
                 } else {
                     System.out.println("动态生成的前台小姐看到 " + simpleName + " 被追到了");
                 }
             } else if ("heiheihei".equals(methodName)) {
                 System.out.println("动态生成的前台小姐安排 " + simpleName + " 开始嘿嘿嘿了");
                 returnValue = method.invoke(object, args);
             } else {
                 returnValue = method.invoke(object, args);
             }
             return returnValue;
         }
     }
    
  2. 费玉清来减肥

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
     public class FeiYuQing {
    
         public static void main(String[] args) {
             // 第一次 500 被代理对象 比基尼辣妹
             IWomen bikiniWoman = new BikiniWoman();
             Object proxyInstance = Proxy.newProxyInstance(bikiniWoman.getClass().getClassLoader(),
                     bikiniWoman.getClass().getInterfaces(),
                     new WomenHandler(bikiniWoman));
             IWomen iWomen = (IWomen)proxyInstance;
             iWomen.run();
             iWomen.heiheihei();
             System.out.println();
             // 第二次 1000 被代理对象 没穿衣服的辣妹
             IWomen nakedWoman = new NakedWoman();
             proxyInstance = Proxy.newProxyInstance(bikiniWoman.getClass().getClassLoader(),
                     bikiniWoman.getClass().getInterfaces(),
                     new WomenHandler(nakedWoman));
             iWomen = (IWomen)proxyInstance;
             iWomen.run();
             iWomen.heiheihei();
             System.out.println();
             // 第三次 2000 被代理对象直接上 Gorilla
             IWomen gorillaWoman = new GorillaWoman();
             proxyInstance = Proxy.newProxyInstance(bikiniWoman.getClass().getClassLoader(),
                     bikiniWoman.getClass().getInterfaces(),
                     new WomenHandler(gorillaWoman));
             iWomen = (IWomen)proxyInstance;
             iWomen.run();
             iWomen.heiheihei();
         }
     }
    

代码运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
动态生成的前台小姐叫 BikiniWoman 起跑了
比基尼辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
动态生成的前台小姐看到 BikiniWoman 被追到了
动态生成的前台小姐安排 BikiniWoman 开始嘿嘿嘿了
比基尼辣妹开始嘿嘿嘿

动态生成的前台小姐叫 NakedWoman 起跑了
没穿衣服的辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
动态生成的前台小姐看到 NakedWoman 被追到了
动态生成的前台小姐安排 NakedWoman 开始嘿嘿嘿了
没穿衣服的辣妹开始嘿嘿嘿

动态生成的前台小姐叫 GorillaWoman 起跑了
Gorilla 说:我追你,如果我追到你,你就让我嘿嘿嘿
动态生成的前台小姐看到 GorillaWoman 追到费玉清了
动态生成的前台小姐安排 GorillaWoman 开始嘿嘿嘿了
费玉清被开始嘿嘿嘿

和静态代理代码不同的是,费玉清每次来到减肥中心见到的前台小姐都是动态创建出来的。

小结

  • 动态代理仅支持接口代理,不支持抽象类代理
  • 不需要编写代理类,但是目标对象一定要实现接口

Cglib 代理(子类代理)

静态代理和动态代理都需要目标对象实现一个接口,依赖接口来实现代理模式,但是有些情况下目标对象只是单独的一个类不需要实现接口,这时候就无法使用动态代理或者静态代理实现代理模式了,需要使用第三种代理模式 Cglib 代理

Cglib 代理,也叫作子类代理,它是在内存中构建一个子类对象,并在子类中采用方法拦截的技术拦截所有父类方法的调用,从而实现对目标对象功能的扩展。但因为采用的是继承,所以不能对 final 修饰的类进行代理,同理,如果目标对象的方法为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础。

Cglib 代理的实现步骤如下:

  1. 创建一个方法拦截器实现 MethodInterceptor 接口,在 intercept 方法中增强对目标对象的实现
  2. 创建 Enhancer 类设置父类以及拦截方法回调,调用 create() 方法创建子类对象(代理对象)
  3. 使用 Cglib 生成的代理对象

示例

使用 Cglib 代理 需要引入 cglib 以及 cglib 依赖的 asm

还是以嘿嘿嘿为例,找比基尼辣妹进行“减肥活动”

  1. 创建一个方法拦截器实现 MethodInterceptor 接口,在 intercept 方法中增强对目标对象的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
     public class CglibMethodInterceptor implements MethodInterceptor {
         private Object target;
    
         public CglibMethodInterceptor(Object target) {
             this.target = target;
         }
    
         @Override
         public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
             String methodName = method.getName();
             String simpleName = target.getClass().getSimpleName();
             Object returnValue;
             if ("run".equals(methodName)) {
                 System.out.println("Cglib 动态生成的子类前台小姐叫 " + simpleName + " 起跑了");
                 // 两种方式调用都可以
                 // returnValue = method.invoke(target, objects);
                 returnValue = methodProxy.invokeSuper(o, objects);
                 if ("GorillaWoman".equals(simpleName)) {
                     System.out.println("Cglib 动态生成的子类前台小姐看到 " + simpleName + " 追到费玉清了");
                 } else {
                     System.out.println("Cglib 动态生成的子类前台小姐看到 " + simpleName + " 被追到了");
                 }
             } else if ("heiheihei".equals(methodName)) {
                 System.out.println("Cglib 动态生成的子类前台小姐安排 " + simpleName + " 开始嘿嘿嘿了");
                 // returnValue = method.invoke(target, objects);
                 returnValue = methodProxy.invokeSuper(o, objects);
             } else {
                 // returnValue = method.invoke(target, objects);
                 returnValue = methodProxy.invokeSuper(o, objects);
             }
             return returnValue;
         }
     }
    
  2. 创建 CglibProxy 类封装 Enhancer 生成子类的逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
     public class CglibProxy {
    
         private Object target;
    
         public CglibProxy(Object target) {
             this.target = target;
         }
    
         //给目标对象创建一个代理对象
         public Object getProxyInstance() {
             //1.工具类
             Enhancer en = new Enhancer();
             //2.设置父类
             en.setSuperclass(target.getClass());
             //3.设置回调函数
             en.setCallback(new CglibMethodInterceptor(target));
             //4.创建子类(代理对象)
             return en.create();
         }
     }
    
  3. 使用 Cglib 生成的代理对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     public class FeiYuQing {
    
         public static void main(String[] args) {
             BikiniWoman bikiniWoman = new BikiniWoman();
             Object proxyInstance = new CglibProxy(bikiniWoman).getProxyInstance();
             BikiniWoman cglibBikiniWoman = (BikiniWoman) proxyInstance;
             cglibBikiniWoman.run();
             cglibBikiniWoman.heiheihei();
         }
     }
    

代码运行结果:

1
2
3
4
5
Cglib 动态生成的子类前台小姐叫 BikiniWoman 起跑了
比基尼辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
Cglib 动态生成的子类前台小姐看到 BikiniWoman 被追到了
Cglib 动态生成的子类前台小姐安排 BikiniWoman 开始嘿嘿嘿了
比基尼辣妹开始嘿嘿嘿

小结

  • Cglib 代理是通过继承目标对象创建子类作为代理对象实现的,可以用与代理一些不需要实现接口的类
  • 由于是通过继承实现的,所以无法代理用 final 修饰的类和方法

总结

  • 代理模式中两个重要的对象就是代理对象和目标对象,代理对象可以对目标对象进行扩展增强,调用者可以通过代理对象访问目标对象。
  • 代理模式的实现有三种:静态代理、动态代理、Cglib 代理,静态代理需要手动创建和维护代理类,动态代理是通过 JDK 的 API 实现动态创建代理对象的,这两种都是依赖接口实现,即目标类都必须实现某个接口,而 Cglib 代理是通过继承目标类创建子类作为代理类实现的,目标类不一定需要实现接口,可以是单独的一个类。
本文由作者按照 CC BY 4.0 进行授权