GoF 23 种设计模式之一:代理模式(Proxy Pattern)

一、代理模式概述

代理模式(Proxy Pattern)是 GoF(《设计模式:可复用面向对象软件的基础》)23 种设计模式之一,属于结构型设计模式。它的核心思想是:为其他对象提供一种代理,以控制对这个对象的访问。当客户端不想或不能直接引用某个对象时,可通过一个“代理”(Proxy)提供间接访问。代理对象既可以在客户端与真实对象(目标对象)之间起到中介作用,还可以对客户端不可见的部分进行过滤,或为客户端添加额外的功能。引入代理的动机在于,通过新的对象来实现对真实对象的操作,或让新的对象作为真实对象的替身,从而达到保护、增强、优化等目的。

1.1 生活场景示例

  • 婚恋介绍(媒婆场景) 牛村的牛二看上了隔壁村的小花,但他不好意思直接接触,于是请媒婆王妈妈作媒。在此场景中:

    • 牛二:目标角色(真实主题)

    • 小花:另一真实主题

    • 王妈妈:代理类(代理主题)

    王妈妈代表牛二先行与小花见面,使得牛二无需直接与小花打交道。在程序设计中,当对象 A 与对象 B 无法直接交互时,可引入代理对象完成中介功能。

  • 房产中介(链家场景) 刚到北京的你想租房,可以自己去跑,也可以找链家中介。你(目标角色)与链家(代理类)都具备“找房子”这一行为,但链家在满足你找房需求的基础上,还会收取一定的服务费。在程序中,当需要在执行某个功能时附加额外逻辑(如权限校验、记录日志等),就可以通过代理模式实现功能增强。

  • 西游记场景(保护机制) 在《西游记》中,猪八戒想强抢高翠兰,孙悟空得知后化身为高翠兰与猪八戒见面。

    • 猪八戒:客户端

    • 高翠兰:真实目标

    • 悟空:代理类

    对猪八戒而言,他以为与的真高翠兰互动,却不知眼前的人是孙悟空。悟空保护了高翠兰,并在不暴露真实身份的情况下完成了干预。代理模式的一个重要特点在于,客户端对代理对象的调用与对目标对象的调用几乎无差别。

  • 编程场景(统一登录验证) 系统中存在 A、B、C 三个模块,访问这些模块之前都要判断用户是否已登录。若将判断逻辑分散写在每个模块中,不仅代码冗余,而且可读性差。此时可为 A、B、C 模块提供一个统一的“登录检查”代理:

    1. 客户端发起请求

    2. 代理先判断用户是否登录

    3. 如果已登录,则调用对应目标模块;否则跳转到登录页面

    这样,代理不仅保护了目标模块,还实现了登录判断代码的复用。

1.2 代理模式的角色

在代理模式中,通常包含以下三类角色:

  1. 抽象主题(Subject) 定义了真实主题和代理主题的公共接口,客户端通过该接口与实际对象或代理对象进行交互。

  2. 真实主题(Real Subject) 即目标类,是真正完成具体业务逻辑的类。例如某个服务的具体实现类。

  3. 代理主题(Proxy) 持有对真实主题的引用,实现与真实主题相同的接口。客户端调用代理主题的方法时,代理可对请求进行预处理(如权限校验、日志记录、性能监控等),然后再调用真实主题的方法。代理还可以根据需要对真实主题的返回值进行后处理。

1.3 代理模式的分类

  • 静态代理(Static Proxy) 在代码编译阶段就已经确定好代理类,通常需要为每个真实主题编写一个对应的代理类,这会导致类数量剧增。静态代理的优点是结构简单,开发成本较低;缺点是耦合度高、可维护性差。

  • 动态代理(Dynamic Proxy) 在运行时动态生成代理类,无需手动编写代理类。常见实现方式包括:

    • JDK 动态代理:只能为实现了接口的类创建代理

    • CGLIB 动态代理:既可以代理接口,也可以代理普通类,基于字节码生成技术(ASM)

    • Javassist 动态代理:同样基于字节码操作,可用于实现 AOP 等功能

二、静态代理

静态代理的实现思路是:为某个接口或类编写一个代理类,代理类持有真实主题对象的引用,在代理方法中添加增强逻辑,再调用真实主题的方法。下面以 OrderService 为例,展示静态代理的三种解决方案。

2.1 接口定义

首先定义一个 OrderService 接口,包含生成订单、修改订单、查看订单明细三个方法:

package com.sangui.mall.service;

/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 订单服务接口
* @Version: 1.0
*/
public interface OrderService {
   /** 生成订单 */
   void generate();

   /** 修改订单信息 */
   void modify();

   /** 查看订单明细 */
   void detail();
}

2.2 真实主题实现

在没有性能监控需求之前,OrderServiceImpl 类直接实现业务逻辑:

package com.sangui.mall.service.impl;

import com.sangui.mall.service.OrderService;

/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 订单服务实现类
* @Version: 1.0
*/
public class OrderServiceImpl implements OrderService {

   @Override
   public void generate() {
       // 假设这是业务逻辑,模拟生成订单耗时
       try {
           Thread.sleep(1234);
      } catch (InterruptedException e) {
           throw new RuntimeException(e);
      }
       System.out.println("订单已生成!!");
  }

   @Override
   public void modify() {
       // 模拟修改订单耗时
       try {
           Thread.sleep(456);
      } catch (InterruptedException e) {
           throw new RuntimeException(e);
      }
       System.out.println("订单已修改!!");
  }

   @Override
   public void detail() {
       // 模拟查询订单明细耗时
       try {
           Thread.sleep(111);
      } catch (InterruptedException e) {
           throw new RuntimeException(e);
      }
       System.out.println("订单请查看!!.........展开");
  }
}

2.3 继承方式(子类继承真实主题)

项目上线一年后,产品经理提出要统计每个业务方法的耗时。最直接的做法是:在每个业务方法内部添加计时代码,但这会改变原有代码且重复度极高,因此不符合开闭原则(OCP)。

一种改进方案是:通过继承 OrderServiceImpl,在子类中重写业务方法,添加计时代码。示例见 OrderServiceImplSub

package com.sangui.mall.service.impl;

/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 继承自 OrderServiceImpl 的子类,用于添加计时代码
* @Version: 1.0
*/
public class OrderServiceImplSub extends OrderServiceImpl {

   @Override
   public void generate() {
       long begin = System.currentTimeMillis();
       super.generate();
       long end = System.currentTimeMillis();
       System.out.println("生成订单耗时:" + (end - begin) + "ms");
  }

   @Override
   public void modify() {
       long begin = System.currentTimeMillis();
       super.modify();
       long end = System.currentTimeMillis();
       System.out.println("修改订单耗时:" + (end - begin) + "ms");
  }

   @Override
   public void detail() {
       long begin = System.currentTimeMillis();
       super.detail();
       long end = System.currentTimeMillis();
       System.out.println("查看订单明细耗时:" + (end - begin) + "ms");
  }
}

缺点

  1. 继承关系耦合度高,不利于后续维护;

  2. 仍未解决多处重复逻辑的问题,如果有多个服务都需要计时,就要为每个服务都写子类,代码复用性差。

2.4 静态代理实现

静态代理通过独立的代理类实现对原有业务的增强。下面给出 OrderServiceProxy 示例。该代理类实现了同样的 OrderService 接口,持有目标对象的引用,并在调用真实方法之前后做计时操作:

package com.sangui.mall.service.impl;

import com.sangui.mall.service.OrderService;

/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 静态代理类
* @Version: 1.0
*/
public class OrderServiceProxy implements OrderService {

   /** 目标对象(真实主题) */
   private OrderService target;

   /** 构造方法:传入真实主题对象 */
   public OrderServiceProxy(OrderService target) {
       this.target = target;
  }

   public OrderServiceProxy() {
  }

   @Override
   public void generate() {
       long begin = System.currentTimeMillis();
       target.generate();
       long end = System.currentTimeMillis();
       System.out.println("生成订单耗时:" + (end - begin) + "ms");
  }

   @Override
   public void modify() {
       long begin = System.currentTimeMillis();
       target.modify();
       long end = System.currentTimeMillis();
       System.out.println("修改订单耗时:" + (end - begin) + "ms");
  }

   @Override
   public void detail() {
       long begin = System.currentTimeMillis();
       target.detail();
       long end = System.currentTimeMillis();
       System.out.println("查看订单明细耗时:" + (end - begin) + "ms");
  }
}

客户端调用示例

package com.sangui.mall.client;

import com.sangui.mall.service.OrderService;
import com.sangui.mall.service.impl.OrderServiceImpl;
import com.sangui.mall.service.impl.OrderServiceProxy;

/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 客户端测试静态代理
* @Version: 1.0
*/
public class Test {
   public static void main(String[] args) {
       // 创建目标对象
       OrderService target = new OrderServiceImpl();
       // 创建代理对象,将目标对象传入
       OrderService proxy = new OrderServiceProxy(target);
       // 通过代理对象调用方法(真实逻辑 + 计时代码)
       proxy.generate();
       proxy.detail();
       proxy.modify();
  }
}

优点

  • 符合开闭原则:在扩展新功能时不修改原有代码,只需要新增代理类;

  • 将公共的增强逻辑集中到代理类,避免重复代码。

缺点

  • 每个真实主题都需要编写一个对应的代理类,当服务数量较多时,会导致类数量爆炸,不易维护。

  • 代理类与真实主题通过接口或继承关联,但仍存在一定耦合。

三、动态代理

动态代理的核心在于:在程序运行阶段动态生成代理类,而无需手动编写每一个代理类,从而减少了类数量。常见技术有 JDK 动态代理、CGLIB 动态代理、Javassist 等。

3.1 JDK 动态代理

JDK 动态代理要求真实主题必须实现接口,通过 java.lang.reflect.Proxy 在运行时创建代理对象。下面继续使用 OrderService 接口与 OrderServiceImpl 类进行演示。

3.1.1 真实主题、接口定义

package com.sangui.mall.service;

/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 订单服务接口
* @Version: 1.0
*/
public interface OrderService {
   /** 测试有返回值的方法 */
   String getName();

   /** 生成订单 */
   void generate();

   /** 修改订单信息 */
   void modify();

   /** 查看订单明细 */
   void detail();
}
package com.sangui.mall.service.impl;

import com.sangui.mall.service.OrderService;

/**
* @Author: sangui
* @CreateTime: 2025-05-31
* @Description: 订单服务实现类
* @Version: 1.0
*/
public class OrderServiceImpl implements OrderService {

   @Override
   public String getName() {
       System.out.println("getName 方法执行!");
       return "zhangsan";
  }

   @Override
   public void generate() {
       // 模拟生成订单耗时
       try {
           Thread.sleep(1234);
      } catch (InterruptedException e) {
           throw new RuntimeException(e);
      }
       System.out.println("订单已生成!!");
  }

   @Override
   public void modify() {
       // 模拟修改订单耗时
       try {
           Thread.sleep(456);
      } catch (InterruptedException e) {
           throw new RuntimeException(e);
      }
       System.out.println("订单已修改!!");
  }

   @Override
   public void detail() {
       // 模拟查询订单明细耗时
       try {
           Thread.sleep(111);
      } catch (InterruptedException e) {
           throw new RuntimeException(e);
      }
       System.out.println("订单请查看!!.........展开");
  }
}

3.1.2 调用处理器(InvocationHandler)

创建一个实现了 InvocationHandler 接口的调用处理器 TimerInvocationHandler,用于在调用任意方法时,打印方法执行耗时:

package com.sangui.mall.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 专门负责计时的调用处理器
* @Version: 1.0
*/
public class TimerInvocationHandler implements InvocationHandler {

   /** 目标对象 */
   private OrderService target;

   public TimerInvocationHandler() {
  }

   public TimerInvocationHandler(OrderService target) {
       this.target = target;
  }

   /**
    * 当代理对象调用方法时,会触发 invoke 方法
    *
    * @param proxy 代理对象
    * @param method 要执行的方法
    * @param args   方法参数
    * @return 返回值
    * @throws Throwable 可能抛出的异常
    */
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       long begin = System.currentTimeMillis();
       // 执行真实主题方法
       Object returnValue = method.invoke(target, args);
       long end = System.currentTimeMillis();
       System.out.println("耗时:" + (end - begin) + "ms");
       return returnValue;
  }
}

3.1.3 客户端使用示例

客户端代码无需手动编写代理类,只需通过 Proxy.newProxyInstance 动态生成代理对象:

package com.sangui.mall.client;

import com.sangui.mall.service.OrderService;
import com.sangui.mall.service.TimerInvocationHandler;
import com.sangui.mall.service.impl.OrderServiceImpl;

import java.lang.reflect.Proxy;

/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 客户端测试 JDK 动态代理
* @Version: 1.0
*/
public class Client {
   public static void main(String[] args) {
       // 创建真实主题对象
       OrderService target = new OrderServiceImpl();

       // 动态生成代理对象
       OrderService proxy = (OrderService) Proxy.newProxyInstance(
           target.getClass().getClassLoader(),            // 类加载器:与目标类保持一致
           target.getClass().getInterfaces(),             // 代理类要实现的接口
           new TimerInvocationHandler(target)             // 调用处理器:实现增强逻辑
      );

       // 通过代理对象调用方法,既执行真实逻辑,也打印耗时
       proxy.detail();
       proxy.modify();
       proxy.generate();
       String name = proxy.getName();
       System.out.println("返回值:" + name);
  }
}

3.1.4 Proxy 工具类封装

为了减少客户端中 Proxy.newProxyInstance(…) 的冗长语句,可对其进行封装。例如在 ProxyUtil 中提供一个静态方法,使客户端调用更简洁:

package com.sangui.mall.utils;

import com.sangui.mall.service.TimerInvocationHandler;
import java.lang.reflect.Proxy;

/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 对 JDK 动态代理的封装工具类
* @Version: 1.0
*/
public class ProxyUtil {

   private ProxyUtil() {
       // 私有化构造函数,防止实例化
  }

   /**
    * 返回代理对象
    * @param target 真实主题对象
    * @return 代理对象
    */
   public static Object newProxyInstance(Object target) {
       return Proxy.newProxyInstance(
           target.getClass().getClassLoader(),
           target.getClass().getInterfaces(),
           new TimerInvocationHandler((com.sangui.mall.service.OrderService) target)
      );
  }
}

客户端代码示例:

// 原本需要写:
// OrderService proxy = (OrderService) Proxy.newProxyInstance(
//     target.getClass().getClassLoader(),
//     target.getClass().getInterfaces(),
//     new TimerInvocationHandler(target)
// );
OrderService proxy = (OrderService) ProxyUtil.newProxyInstance(target);

3.2 CGLIB 动态代理

CGLIB(Code Generation Library)是一种基于字节码生成的代理技术,既可以代理接口,也可以直接代理普通类(通过继承方式)。它底层依赖 ASM 框架,使得生成的代理类性能优于 JDK 动态代理。需要注意的是,被代理的目标类不能使用 final 修饰,否则无法生成子类。

3.2.1 引入依赖

pom.xml 中添加 CGLIB 依赖:

<dependency>
   <groupId>cglibgroupId>
   <artifactId>cglibartifactId>
   <version>3.3.0version>
dependency>

3.2.2 目标类示例

下面以一个用户服务 UserService 为例,不再需要特定接口,直接编写普通类即可:

package com.sangui.mall.service;

/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 用户服务类(无接口)
* @Version: 1.0
*/
public class UserService {

   /**
    * 登录方法
    * @param username 用户名
    * @param password 密码
    * @return 验证结果
    */
   public boolean login(String username, String password) {
       System.out.println("正在验证身份……");
       return "admin".equals(username) && "123".equals(password);
  }

   /** 退出方法 */
   public void logout() {
       System.out.println("退出系统中……");
  }
}

3.2.3 方法拦截器(MethodInterceptor)

CGLIB 的代理通过 MethodInterceptor 拦截器实现增强逻辑。在 intercept 方法中,可在调用真实方法前后完成计时或其他操作:

package com.sangui.mall.service;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: CGLIB 方法拦截器,用于添加计时功能
* @Version: 1.0
*/
public class TimerMethodInterceptor implements MethodInterceptor {

   /**
    * @param obj         代理对象
    * @param method     即将调用的方法
    * @param args       方法参数
    * @param methodProxy CGLIB 方法代理,用于调用父类方法
    * @return 方法返回值
    * @throws Throwable 可能抛出的异常
    */
   @Override
   public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
           throws Throwable {
       long begin = System.currentTimeMillis();
       // 调用父类(真实主题)的方法
       Object returnValue = methodProxy.invokeSuper(obj, args);
       long end = System.currentTimeMillis();
       System.out.println("耗时:" + (end - begin) + "ms");
       return returnValue;
  }
}

3.2.4 客户端使用示例

客户端通过 CGLIB 的 Enhancer 类生成代理对象,无需手动编写任何代理类:

package com.sangui.mall.client;

import com.sangui.mall.service.TimerMethodInterceptor;
import com.sangui.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;

/**
* @Author: sangui
* @CreateTime: 2025-06-01
* @Description: 客户端测试 CGLIB 动态代理
* @Version: 1.0
*/
public class Client {
   public static void main(String[] args) {
       // 创建 CGLIB 增强器对象
       Enhancer enhancer = new Enhancer();

       // 指定父类,即真实主题类
       enhancer.setSuperclass(UserService.class);

       // 设置拦截器(相当于 JDK 的 InvocationHandler)
       enhancer.setCallback(new TimerMethodInterceptor());

       // 创建代理对象:Step1 生成子类字节码,Step2 实例化
       UserService proxy = (UserService) enhancer.create();

       // 通过代理对象调用方法,将打印计时信息
       proxy.login("admin", "123");
       proxy.login("admin", "123456");
       proxy.logout();
  }
}

注意 在 JDK 9 及以上版本使用 CGLIB 时,可能需要在启动参数中添加以下配置:

--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED

以便开放相关模块,允许 CGLIB 的字节码生成和反射操作。

四、代理模式适用场景与优缺点

4.1 适用场景

  1. 远程代理(Remote Proxy) 客户端通过代理对象访问远程主机上的资源,如 RMI 中的远程服务访问。

  2. 虚拟代理(Virtual Proxy) 当真实对象体积过大、创建开销较高时,可用代理对象延迟创建真实对象。例如:图像加载时,先显示占位图,点击或达到一定条件后再加载真实图像。

  3. 安全代理/防护代理(Protection Proxy) 为真实对象提供访问控制,根据权限判断是否允许访问。例如:在敏感业务前进行登录校验、权限检查。

  4. 缓存代理(Cache Proxy) 对一些耗时操作的结果进行缓存,下次相同请求直接返回缓存结果,避免重复计算。

  5. 日志/计时代理(Logging/Timing Proxy) 用于记录方法调用日志或统计执行时间,可在不修改真实业务代码的前提下,实现同样效果。

4.2 优缺点

  • 优点

    1. 代理对象可以在客户端与真实对象之间起到中介作用,并在不修改真实对象代码的前提下进行功能扩展;

    2. 客户端与代理对象交互时,不需要区分代理或真实对象,易于维护;

    3. 动态代理技术大大简化了代理类的编写,提高了代码复用性。

  • 缺点

    1. 静态代理会导致类数量剧增,不易维护;

    2. 动态代理在首次生成代理类时会有一定性能损耗(如 JDK 代理创建较慢);

    3. CGLIB 代理需要依赖字节码生成库,对启动时机和运行环境有一定要求。

五、总结

本文从生活化场景出发,深入剖析了代理模式的核心思想和应用动机;并分别介绍了静态代理与动态代理(JDK 与 CGLIB)的实现方式与示例代码。代理模式允许我们在不修改现有业务逻辑的情况下,为目标对象提供访问控制、日志统计、性能监控等额外功能。此外,动态代理技术的出现大大简化了代理类的编写工作,提高了系统的可维护性与可扩展性。读者在实际项目中,可根据需求选择合适的代理方案,为系统添加灵活的横切功能。

  • 微信
  • 赶快加我聊天吧
  • QQ
  • 赶快加我聊天吧
  • weinxin
三桂

发表评论 取消回复 您未登录,登录后才能评论,前往登录

    • avatar

      您真牛逼