GoF 代理模式详解

GoF 代理模式详解

一、什么是代理模式

代理模式(Proxy Pattern)是 GoF 23 种设计模式中的一种结构型模式。它的核心思想是:为其他对象提供一种代理,以控制对这个对象的访问。

通俗理解:你不想或不能直接访问某个对象时,通过一个"中间人"来访问。中间人可以在访问前后做一些额外处理,比如权限校验、日志记录、延迟加载、事务控制等。

生活中的例子:租房找中介。房东是真实对象,中介是代理。租客不直接找房东,而是通过中介看房、签合同。中介可以在带看前审核租客资质,签合同后收中介费。

二、代理模式的结构

代理模式包含三个角色:

角色 说明
抽象主题(Subject) 定义真实对象和代理对象的公共接口
真实主题(RealSubject) 真正执行业务逻辑的对象
代理(Proxy) 持有真实对象的引用,控制对真实对象的访问

代理对象和真实对象实现同一个接口,客户端面向接口编程,不关心背后是真实对象还是代理。

三、静态代理

静态代理是最简单的实现方式:手动编写代理类,实现与目标类相同的接口,在方法调用前后插入增强逻辑。

代码示例

抽象主题:

java 复制代码
public interface UserService {
    void save(User user);
    User findById(Long id);
}

真实主题:

java 复制代码
public class UserServiceImpl implements UserService {
    @Override
    public void save(User user) {
        System.out.println("保存用户:" + user.getName());
    }
    
    @Override
    public User findById(Long id) {
        System.out.println("查询用户:" + id);
        return new User(id, "张三");
    }
}

代理:

java 复制代码
public class UserServiceProxy implements UserService {
    private final UserService target;
    
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void save(User user) {
        System.out.println("【日志】开始保存用户");
        target.save(user);
        System.out.println("【日志】保存用户完成");
    }
    
    @Override
    public User findById(Long id) {
        System.out.println("【日志】开始查询用户");
        User user = target.findById(id);
        System.out.println("【日志】查询用户完成");
        return user;
    }
}

客户端:

java 复制代码
public class Client {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(target);
        proxy.save(new User(1L, "张三"));
    }
}

静态代理的缺点

  • 类数量翻倍:每个目标类都要写一个代理类。
  • 维护成本高:接口新增方法时,真实类和代理类都要修改。
  • 重复代码:多个代理类的增强逻辑相似,无法复用。

静态代理只适合目标类少、增强逻辑简单的场景。

四、动态代理

动态代理在运行时动态生成代理类,不需要手动编写。Java 中有两种主流实现:JDK 动态代理和 CGLIB 动态代理。

1. JDK 动态代理

JDK 动态代理基于接口,要求目标类必须实现至少一个接口。核心是 java.lang.reflect.Proxy 和 InvocationHandler。

java 复制代码
public class JdkProxyHandler implements InvocationHandler {
    private final Object target;
    
    public JdkProxyHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【日志】调用方法:" + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("【日志】方法执行完成");
        return result;
    }
}

生成代理对象:

java 复制代码
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new JdkProxyHandler(target)
);
proxy.save(new User(1L, "张三"));

特点:

  • 基于接口,目标类必须实现接口。
  • 代理类和目标类实现同一个接口。
  • 通过 InvocationHandler.invoke() 拦截所有方法调用。
  • 生成代理类速度快,但调用性能略低于直接调用。

2. CGLIB 动态代理

CGLIB(Code Generation Library)通过继承目标类生成子类来实现代理。目标类不需要实现接口,但不能是 final 类,被代理的方法不能是 final 或 private。

java 复制代码
public class CglibProxyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, 
                            MethodProxy proxy) throws Throwable {
        System.out.println("【日志】调用方法:" + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("【日志】方法执行完成");
        return result;
    }
}

生成代理对象:

java 复制代码
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new CglibProxyInterceptor());
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.save(new User(1L, "张三"));

特点:

  • 基于继承,不需要接口。
  • 代理类是目标类的子类。
  • 无法代理 final 类和 final 方法。
  • 生成代理类较慢,但调用性能优于 JDK 动态代理。

3. JDK 动态代理 vs CGLIB

对比维度 JDK 动态代理 CGLIB
代理方式 基于接口 基于继承
目标类要求 必须实现接口 不能是 final 类
代理类 实现同一接口 继承目标类
方法限制 只能代理接口中定义的方法 不能代理 final/private 方法
性能 生成快,调用稍慢 生成慢,调用快
依赖 JDK 内置 需要 CGLIB 库
Spring 默认 有接口时使用 无接口时使用

五、代理模式在 Spring 中的应用

Spring AOP 的底层就是动态代理。当 Bean 需要被增强时(如 @Transactional、@Cacheable、@Async、自定义切面),Spring 会为 Bean 生成代理对象,放入容器。

1. 代理选择规则

  • 目标类实现了接口,默认使用 JDK 动态代理。
  • 目标类没有实现接口,使用 CGLIB。
  • 可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用 CGLIB。
  • Spring Boot 2.x 之后,AOP 自动配置默认 proxyTargetClass = true,即默认使用 CGLIB。

2. 代理对开发的影响

内部调用失效: 同一个类中方法 A 调用方法 B,如果 B 需要被 AOP 增强,不会生效。因为内部调用走的是原始对象,不是代理对象。

java 复制代码
@Service
public class UserService {
    public void methodA() {
        methodB();  // 直接调用,不经过代理
    }
    
    @Transactional
    public void methodB() {
        // 事务不会生效
    }
}

注入的是代理对象: 从容器中 getBean() 拿到的可能是代理对象,不是原始对象。所以 bean.getClass() 可能不是原始类。

3. Spring 中代理的典型场景

  • @Transactional:事务代理,在方法前后开启/提交/回滚事务。
  • @Cacheable:缓存代理,先查缓存,未命中才执行方法。
  • @Async:异步代理,把方法调用提交到线程池。
  • Spring Security:安全代理,方法调用前校验权限。
  • 自定义 AOP 切面:日志、监控、限流等。

六、代理模式 vs 装饰器模式

两者都通过组合持有目标对象,都实现同一接口,容易混淆。

对比维度 代理模式 装饰器模式
目的 控制对目标对象的访问 动态给目标对象添加功能
关注点 访问控制、延迟加载、权限 功能增强、组合扩展
目标对象 通常自己创建或注入 由客户端传入
代理数量 通常一个代理对应一个目标 可以多层装饰
典型场景 事务、权限、远程调用 IO 流、Java 集合

代理模式侧重"控制",装饰器模式侧重"增强"。但在实际应用中,两者的边界有时模糊,Spring AOP 既有控制(事务)也有增强(日志)。

七、代理模式的优缺点

优点

  • 降低耦合:客户端面向接口,不直接依赖真实对象。
  • 增强功能:在访问前后插入日志、事务、权限等横切逻辑。
  • 控制访问:可以限制对真实对象的访问,实现延迟加载、权限校验。
  • 符合开闭原则:新增代理不需要修改真实对象和客户端。

缺点

  • 类数量增加:静态代理需要为每个目标类写代理类。
  • 性能开销:动态代理通过反射调用,比直接调用慢。
  • 调试复杂:代理链层层嵌套,栈跟踪不直观。
  • 内部调用失效:Spring AOP 中同一个类内部方法调用不经过代理。

八、适用场景

场景 说明
远程代理 隐藏远程调用的网络细节,如 RPC 框架
虚拟代理 延迟加载大对象,如大图、大文件
保护代理 权限校验,如 Spring Security
缓存代理 缓存查询结果,如 @Cacheable
日志代理 记录方法调用日志
事务代理 声明式事务管理

九、总结

维度 核心要点
本质 为其他对象提供代理,控制对它的访问
角色 抽象主题、真实主题、代理
静态代理 手动编写代理类,类数量多,维护成本高
JDK 动态代理 基于接口,Proxy + InvocationHandler
CGLIB 动态代理 基于继承,Enhancer + MethodInterceptor
Spring AOP 底层是动态代理,有接口用 JDK,无接口用 CGLIB
关键问题 内部调用失效、注入的是代理对象
典型应用 事务、缓存、异步、权限、日志

代理模式是 Spring AOP 的基石。理解静态代理和两种动态代理的实现原理,就能理解 Spring 如何在不修改业务代码的情况下,为 Bean 添加事务、缓存、日志等横切能力。同时也能解释为什么 @Transactional 在同类内部调用时会失效------因为内部调用没有经过代理对象。

相关推荐
IPdodo_17 天前
住宅 IP 与数据中心 IP 到底差在哪:用 RDAP、BGP 和多源证据做工程判定
网络·网络安全·代理模式·ip
承渊政道22 天前
PR-Agent鸿蒙PC适配全记录:从Python服务端代理到ArkTS原生评审客户端
python·华为·代理模式·agent·harmonyos·pc端
kruptos1 个月前
动态规划:暴力递归如何变优雅?
代理模式·算法优化·斐波那契
ydd1001001 个月前
正则表达式匹配
正则表达式·代理模式
LayZhangStrive1 个月前
设计模式(二)装饰器模式、代理模式(待更新)
设计模式·代理模式·装饰器模式·后端开发
Lam Tang2 个月前
APS 系列文章10
java·代理模式
Lam Tang2 个月前
APS 系列文章 08
java·代理模式
wangjialelele2 个月前
动态规划DP经典题型总结(Java、C++):路径、子数组、子序列、背包问题详解
java·c++·算法·面试·动态规划·代理模式
聪明蛋子哟2 个月前
从MCP到A2A再到Proxy:Agent-工具协议的三种代理模式
代理模式