跳转至

代理模式

从糖果机远程监控说起

公司在全国各地部署了上千台糖果机(GumballMachine),CEO 想在办公室的监视器上看到每台机器当前状态——剩余糖果数量、所在位置、当前状态。

问题是:GumballMachine 运行在不同的 JVM 进程(远程机器)上,你不能直接调用它的方法。

解决方案是创建一个**远程代理**:本地的 GumballMachineProxy 实现和 GumballMachine 相同的接口,当你调用 proxy.getCount() 时,它透明地通过网络转发给真正的机器,拿回结果。监视器(客户端)完全感知不到这个调用穿越了网络。

这是代理模式的第一个经典变体——远程代理。书中还介绍了**虚拟代理**(延迟创建开销大的对象)和**保护代理**(控制访问权限),三者结构相同,意图不同。

🔍 定义

代理模式(Proxy)为其他对象提供一种代理,以控制对这个对象的访问。代理与真实对象实现相同接口,对调用方完全透明。

代理类型 意图 典型场景
远程代理(Remote Proxy) 控制对远程对象的访问 RPC、RMI、REST 客户端包装
虚拟代理(Virtual Proxy) 延迟创建开销大的对象 图片懒加载、Hibernate 懒加载
保护代理(Protection Proxy) 控制访问权限 Spring Security、权限拦截
缓存代理(Cache Proxy) 缓存远程/开销大的结果 Spring @Cacheable

⚠️ 不使用代理存在的问题

直接使用 UserService 时,每次都需要手动处理访问控制、日志记录和缓存:

ProxyBadExample.java
package com.example.structural.proxy;

/**
 * 代理模式 - 反例
 * 问题:UserServiceImpl 混杂了缓存、鉴权、日志等横切关注点,职责不单一
 */
public class ProxyBadExample {
    public static void main(String[] args) {
        UserServiceImplBad service = new UserServiceImplBad();
        System.out.println(service.findUser(1L));
        System.out.println(service.findUser(1L)); // 没有缓存,再次查数据库 ❌
    }
}

class UserBad {
    private final Long   id;
    private final String name;
    public UserBad(Long id, String name) { this.id = id; this.name = name; }
    @Override public String toString() { return "User{id=" + id + ", name=" + name + "}"; }
}

// ❌ 业务逻辑与缓存/日志/鉴权混在一起
class UserServiceImplBad {
    public UserBad findUser(Long id) {
        // ❌ 缓存逻辑混入业务代码
        System.out.println("[缓存检查] 无缓存(每次都要检查)");
        // ❌ 日志混入业务代码
        System.out.println("[日志] 开始查询用户 " + id);
        long start = System.currentTimeMillis();

        // 模拟数据库查询
        UserBad user = queryFromDb(id);

        long elapsed = System.currentTimeMillis() - start;
        System.out.println("[日志] 查询完成,耗时 " + elapsed + "ms");
        return user;
    }

    private UserBad queryFromDb(Long id) {
        System.out.println("[数据库] SELECT * FROM user WHERE id = " + id);
        return new UserBad(id, "张三");
    }
}

🏗️ 设计模式结构说明

%%{init: {'themeVariables': {'noteBkgColor': 'transparent', 'noteBorderColor': '#768390'}}}%%
classDiagram
    classDef default fill:transparent,stroke:#768390
    class UserService {
        <<interface>>
        +getUserById(id Long) User
        +updateUser(user User) void
    }
    class UserServiceImpl {
        +getUserById(id Long) User
        +updateUser(user User) void
    }
    class CachingUserServiceProxy {
        -real: UserService
        -cache: Map
        +getUserById(id Long) User
        +updateUser(user User) void
    }
    UserService <|.. UserServiceImpl
    UserService <|.. CachingUserServiceProxy
    CachingUserServiceProxy o--> UserService
    Client ..> UserService
    note for UserService "主体接口(Subject)"
    note for UserServiceImpl "真实主体(RealSubject)"
    note for CachingUserServiceProxy "代理(Proxy)"
    note for Client "客户端(Client)"

代理(CachingUserServiceProxy)与真实对象实现相同接口,客户端只依赖接口,无感知地通过代理访问真实对象。

💻 设计模式举例说明

ProxyExample.java
package com.example.structural.proxy;

import java.util.HashMap;
import java.util.Map;

/**
 * 代理模式 - 正例
 * CachingUserServiceProxy 在不修改 UserServiceImpl 的情况下添加缓存能力
 */
public class ProxyExample {
    public static void main(String[] args) {
        // ✅ 代理透明地为真实服务添加缓存
        UserService service = new CachingUserServiceProxy(new UserServiceImpl());

        System.out.println(service.findUser(1L)); // 第一次:查数据库
        System.out.println(service.findUser(1L)); // 第二次:命中缓存 ✅
        System.out.println(service.findUser(2L)); // 不同 ID:查数据库
    }
}

// 用户领域对象
class User {
    private final Long   id;
    private final String name;
    public User(Long id, String name) { this.id = id; this.name = name; }
    @Override public String toString() { return "User{id=" + id + ", name=" + name + "}"; }
}

// 服务接口
interface UserService {
    User findUser(Long id);
}

// 真实对象:只专注于业务逻辑
class UserServiceImpl implements UserService {
    @Override
    public User findUser(Long id) {
        System.out.println("[数据库] SELECT * FROM user WHERE id = " + id);
        return new User(id, "张三" + id);
    }
}

// ✅ 代理:在不修改 UserServiceImpl 的前提下添加缓存
class CachingUserServiceProxy implements UserService {
    private final UserService        target; // 持有真实对象的引用
    private final Map<Long, User>    cache = new HashMap<>();

    public CachingUserServiceProxy(UserService target) { this.target = target; }

    @Override
    public User findUser(Long id) {
        // 前置处理:检查缓存
        if (cache.containsKey(id)) {
            System.out.println("[缓存] 命中 user:" + id + " ✅");
            return cache.get(id);
        }
        // 委托真实对象处理
        User user = target.findUser(id);
        // 后置处理:写入缓存
        cache.put(id, user);
        System.out.println("[缓存] 写入 user:" + id);
        return user;
    }
}

🔄 三种常见代理类型

类型 创建时机 是否需要接口 核心 API
静态代理 编译期手动编写 ✅ 需要 手动实现接口
JDK 动态代理 运行时自动生成 ✅ 需要 Proxy.newProxyInstance() + InvocationHandler
CGLIB 动态代理 运行时通过字节码生成子类 ❌ 不需要 Enhancer + MethodInterceptor

静态代理

代理类在**编译期**就已手动编写完成。优点是实现简单、直观;缺点是每新增一个接口方法,代理类也要同步修改,接口越多代理类越多。

StaticProxyExample.java
package com.example.structural.proxy.static_proxy;

/**
 * 静态代理示例
 * <p>
 * 代理类在编译期就已手动编写完成,每个接口都需要一个独立的代理类。
 * 优点:实现简单、直观;缺点:接口越多代理类越多,维护成本随之增加。
 */
public class StaticProxyExample {
    public static void main(String[] args) {
        // 静态代理:编译期已确定代理类,运行时直接 new
        OrderService service = new LoggingOrderProxy(new RealOrderService());
        service.createOrder("商品A", 2);
        service.cancelOrder(1001L);
    }
}

interface OrderService {
    Long createOrder(String product, int quantity);
    void cancelOrder(Long orderId);
}

// 真实业务类,只专注于核心逻辑
class RealOrderService implements OrderService {
    private long nextId = 1000L;

    @Override
    public Long createOrder(String product, int quantity) {
        System.out.printf("[业务] 创建订单:%s x%d%n", product, quantity);
        return ++nextId;
    }

    @Override
    public void cancelOrder(Long orderId) {
        System.out.println("[业务] 取消订单:" + orderId);
    }
}

// ✅ 静态代理:手动编写,实现相同接口,织入日志横切逻辑
class LoggingOrderProxy implements OrderService {
    private final OrderService target;

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

    @Override
    public Long createOrder(String product, int quantity) {
        System.out.printf("[日志] → createOrder(%s, %d)%n", product, quantity);
        Long id = target.createOrder(product, quantity);
        System.out.println("[日志] ← createOrder 返回 orderId=" + id);
        return id;
    }

    @Override
    public void cancelOrder(Long orderId) {
        System.out.println("[日志] → cancelOrder(" + orderId + ")");
        target.cancelOrder(orderId);
        System.out.println("[日志] ← cancelOrder 完成");
    }
}

JDK 动态代理

利用 java.lang.reflect.Proxy 在**运行时**自动生成代理类,无需为每个接口单独编写代理。所有方法调用都汇聚到 InvocationHandler.invoke() 统一拦截处理,一个 InvocationHandler 可复用于任意接口。

限制:被代理对象必须实现接口(代理类是接口的实现类,不是目标类的子类)。

JdkDynamicProxyExample.java
package com.example.structural.proxy.jdk_proxy;

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

/**
 * JDK 动态代理示例
 * <p>
 * 使用 java.lang.reflect.Proxy 在运行时自动生成代理类,无需为每个接口手动编写代理类。
 * 核心:实现 InvocationHandler,所有方法调用都汇聚到 invoke() 统一处理。
 * <p>
 * 限制:被代理对象必须实现接口(代理类是接口的实现,不是目标类的子类)。
 */
public class JdkDynamicProxyExample {
    public static void main(String[] args) {
        OrderService real = new RealOrderService();

        // ✅ 同一个 LoggingHandler 可代理任意接口,无需为每个接口单独写代理类
        OrderService service = (OrderService) Proxy.newProxyInstance(
                real.getClass().getClassLoader(),    // 使用目标类的类加载器
                new Class[]{OrderService.class},     // 代理的接口列表
                new LoggingHandler(real)             // 方法调用委托给 InvocationHandler
        );

        service.createOrder("商品B", 3);
        service.cancelOrder(2001L);
    }
}

interface OrderService {
    Long createOrder(String product, int quantity);
    void cancelOrder(Long orderId);
}

class RealOrderService implements OrderService {
    private long nextId = 2000L;

    @Override
    public Long createOrder(String product, int quantity) {
        System.out.printf("[业务] 创建订单:%s x%d%n", product, quantity);
        return ++nextId;
    }

    @Override
    public void cancelOrder(Long orderId) {
        System.out.println("[业务] 取消订单:" + orderId);
    }
}

// ✅ 通用拦截器:持有 Object 引用,可为任意类型的目标对象生成代理
class LoggingHandler implements InvocationHandler {
    private final Object target;

    public LoggingHandler(Object target) { this.target = target; }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置日志
        System.out.printf("[日志] → %s(%s)%n", method.getName(), formatArgs(args));
        // 反射调用真实方法
        Object result = method.invoke(target, args);
        // 后置日志
        System.out.printf("[日志] ← %s 返回: %s%n", method.getName(), result);
        return result;
    }

    private String formatArgs(Object[] args) {
        if (args == null || args.length == 0) return "无参数";
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            if (i > 0) sb.append(", ");
            sb.append(args[i]);
        }
        return sb.toString();
    }
}

CGLIB 动态代理

CGLIB 通过字节码工具在**运行时**生成目标类的**子类**作为代理,无需目标类实现接口。Spring AOP 在目标类没有接口时默认使用此方式(@Transactional@Cacheable 等本质上都是 CGLIB 代理)。

限制:无法代理 final 类或 final 方法(子类无法覆写)。Java 17+ 运行需要 --add-opens JVM 参数;Spring Boot 项目无需手动配置,框架已处理。

CglibProxyExample.java
package com.example.structural.proxy.cglib_proxy;

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

import java.lang.reflect.Method;

/**
 * CGLIB 动态代理示例
 * <p>
 * CGLIB 通过字节码生成目标类的子类作为代理,无需目标类实现任何接口。
 * Spring AOP 在目标类没有接口时默认使用此方式。
 * <p>
 * 限制:无法代理 final 类或 final 方法(子类无法覆写)。
 * Java 17+ 运行需要 --add-opens java.base/java.lang=ALL-UNNAMED;Spring Boot 已自动处理。
 */
public class CglibProxyExample {
    public static void main(String[] args) {
        // ✅ CGLIB:直接代理普通类,无需接口——这是与 JDK 代理的核心区别
        OrderService service = CglibProxyFactory.createLoggingProxy(new OrderService());
        service.createOrder("商品C", 5);
        service.cancelOrder(3001L);
    }
}

// ✅ 普通类,无需实现任何接口
class OrderService {
    private long nextId = 3000L;

    public Long createOrder(String product, int quantity) {
        System.out.printf("[业务] 创建订单:%s x%d%n", product, quantity);
        return ++nextId;
    }

    public void cancelOrder(Long orderId) {
        System.out.println("[业务] 取消订单:" + orderId);
    }
}

// CGLIB 代理工厂:使用 Enhancer 生成继承目标类的子类
class CglibProxyFactory {
    @SuppressWarnings("unchecked")
    public static <T> T createLoggingProxy(T target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 继承目标类
        enhancer.setCallback(new LoggingInterceptor());
        // 生成子类实例(代理 IS-A 目标类,无需接口)
        return (T) enhancer.create();
    }
}

// 方法拦截器:拦截子类的所有方法调用,织入横切逻辑
class LoggingInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.printf("[日志] → %s(%s)%n", method.getName(), formatArgs(args));
        // invokeSuper 调用父类(原始类)的方法,不会触发再次拦截
        Object result = proxy.invokeSuper(obj, args);
        System.out.printf("[日志] ← %s 返回: %s%n", method.getName(), result);
        return result;
    }

    private String formatArgs(Object[] args) {
        if (args == null || args.length == 0) return "无参数";
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            if (i > 0) sb.append(", ");
            sb.append(args[i]);
        }
        return sb.toString();
    }
}

⚖️ 优缺点

优点:

  • 在不修改真实对象的前提下,透明地添加访问控制、缓存、日志等横切逻辑
  • 符合**开闭原则**:新增横切逻辑只需新增代理类
  • 支持延迟初始化(虚拟代理)

缺点:

  • 每个接口需要一个代理类(静态代理),代码量增多
  • 动态代理增加了一定的反射开销

🔗 与其它模式的关系

相似模式防混淆:

模式 接口变化? 对象生命周期 主要意图
代理(Proxy) ❌ 不变 代理通常自行创建/管理真实对象 控制访问
装饰器(Decorator) ❌ 不变 被装饰对象由客户端传入 动态增强功能
适配器(Adapter) ✅ 改变 兼容接口

🗂️ 应用场景

  • 访问控制(保护代理):调用前检查权限
  • 延迟加载(虚拟代理):大对象只在首次访问时才真正创建
  • 缓存(缓存代理):对频繁访问的结果进行缓存
  • Spring AOP:@Transactional@Cacheable@Async 底层都是动态代理
  • MyBatis:Mapper 接口没有实现类,调用时是 JDK 动态代理执行 SQL

🏭 工业视角

从"业务代码被污染"到动态代理的演进

王争在《设计模式之美》中用一个性能计数器(MetricsCollector)的例子清晰地展现了代理模式的动机:当监控、日志等非业务代码直接写在 UserController 里时,业务类的职责就被污染了,替换框架的成本也极高。

静态代理虽然能解耦,但有一个致命缺陷——接口有多少方法,代理类就要重写多少方法,50 个原始类就要写 50 个代理类。动态代理正是为消除这种模板式重复而生的:

JDK 动态代理:一个 Handler 代理所有接口
public class MetricsCollectorProxy {
    private MetricsCollector metricsCollector = new MetricsCollector();

    public Object createProxy(Object proxiedObject) {
        Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
        return Proxy.newProxyInstance(
            proxiedObject.getClass().getClassLoader(),
            interfaces,
            (proxy, method, args) -> {
                long start = System.currentTimeMillis();
                Object result = method.invoke(proxiedObject, args);
                long cost = System.currentTimeMillis() - start;
                metricsCollector.recordRequest(
                    new RequestInfo(method.getName(), cost, start));
                return result;
            });
    }
}

JDK 代理 vs CGLIB 代理的选择依据

JDK 动态代理要求目标类实现接口,代理对象是接口的实现类。CGLIB 通过字节码生成目标类的子类,无需接口,但无法代理 final 类/方法。Spring AOP 的默认策略是:有接口用 JDK 代理,无接口用 CGLIB。Spring Boot 2.x 起默认对所有 Bean 使用 CGLIB,可通过 spring.aop.proxy-target-class=false 切回 JDK 代理。

代理的本质:附加"与业务无关"的横切逻辑

理解代理模式的关键在于区分它和装饰器模式的**意图差异**:

模式 附加的逻辑与原始类的关系 典型场景
代理(Proxy) 无关——监控、鉴权、限流、事务,这些与业务逻辑本身无关 Spring AOP、RPC stub
装饰器(Decorator) 相关——对原有功能的增强,如 BufferedInputStream 增强 read() Java IO、功能叠加

代理 ≠ 装饰器,虽然结构几乎相同

两者代码结构高度相似(都持有同接口的对象引用),但**意图截然不同**。代理关注"访问控制",被代理对象通常由代理自己创建/管理;装饰器关注"功能增强",被装饰对象由调用方传入。Spring AOP 的 @Transactional 是代理(事务与业务无关);Java IO 的 BufferedInputStream 是装饰器(缓冲是对读取功能的增强)。