【设计模式】【结构型模式】代理模式(Proxy)

发布于:2025-02-19 ⋅ 阅读:(27) ⋅ 点赞:(0)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云

一、入门

什么是代理模式?

代理模式(Proxy Pattern)是一种结构型设计模式,允许你提供一个代理对象来控制对另一个对象的访问。
代理对象在客户端和目标对象之间起到中介作用,可以在不改变目标对象的情况下增加额外的功能或控制访问。

为什么要代理模式?

  1. 违反单一职责原则
    • 原始对象的核心职责是实现业务逻辑,但如果将额外的功能(如权限检查、日志记录等)直接写入原始对象,会导致对象的职责变得复杂,难以维护。
    • 例如,一个UserService类如果既要处理用户登录逻辑,又要记录日志、检查权限,代码会变得臃肿。
  2. 代码重复
    • 如果多个地方需要对对象的访问进行相同的控制(如权限检查),开发者可能会在每个调用点重复编写相同的代码,导致代码冗余。
  3. 难以扩展
    • 如果需要在访问对象时增加新的功能(如缓存、延迟加载等),可能需要直接修改原始对象的代码,这会破坏开闭原则(对扩展开放,对修改关闭)。
  4. 性能问题
    • 某些场景下,对象的创建或初始化成本较高(如加载大文件、连接远程服务等)。如果没有代理模式,可能会在不需要时提前创建对象,导致资源浪费。
  5. 安全性问题
    • 如果没有代理模式,客户端可以直接访问敏感对象,可能会绕过必要的安全检查或权限控制。

如何实现代理模式?

  1. Subject(抽象主题):定义真实对象和代理对象的共同接口,客户端通过该接口与真实对象交互。
  2. RealSubject(真实主题):实现Subject接口,是代理对象所代表的真实对象。
  3. Proxy(代理):实现Subject接口,持有对RealSubject的引用,控制对RealSubject的访问,并可以在访问前后添加额外操作。

【案例】订单加强
网购订单处理:假设我们需要在用户下单时记录日志,但不想修改核心的订单处理逻辑。

静态代理

Subject(抽象主题): OrderService接口,定义订单下达。

public interface OrderService {
    void createOrder(String product);
}

RealSubject(真实主题)OrderServiceImpl类,实现下单逻辑。

public class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder(String product) {
        System.out.println("订单创建成功,商品:" + product);
    }
}

Proxy(代理)OrderServiceStaticProxy(手动编写代理类,添加日志记录)

public class OrderServiceStaticProxy implements OrderService {
    private OrderService orderService;

    public OrderServiceStaticProxy(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void createOrder(String product) {
        System.out.println("[静态代理] 记录日志:开始下单");
        orderService.createOrder(product);
        System.out.println("[静态代理] 记录日志:下单完成");
    }
}

测试类

public class Client {
    public static void main(String[] args) {
        OrderService realService = new OrderServiceImpl();
        OrderService proxy = new OrderServiceStaticProxy(realService);
        proxy.createOrder("iPhone 15");
    }
}

输出

[静态代理] 记录日志:开始下单
订单创建成功,商品:iPhone 15
[静态代理] 记录日志:下单完成

JDK代理

通过JDK的InvocationHandlerProxy类动态生成代理对象。
Subject(抽象主题): OrderService接口,定义订单下达。

public interface OrderService {
    void createOrder(String product);
}

RealSubject(真实主题)OrderServiceImpl类,实现下单逻辑。

public class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder(String product) {
        System.out.println("订单创建成功,商品:" + product);
    }
}

Proxy(代理)InvocationHandler类,InvocationHandler接口。

public class LogInvocationHandler implements InvocationHandler {
    private Object target; // 真实对象

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[JDK动态代理] 记录日志:开始执行方法 " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("[JDK动态代理] 记录日志:方法执行完成");
        return result;
    }
}

客户端调用

public class Client {
    public static void main(String[] args) {
        OrderService realService = new OrderServiceImpl();
        OrderService proxy = (OrderService) Proxy.newProxyInstance(
            realService.getClass().getClassLoader(),
            realService.getClass().getInterfaces(),
            new LogInvocationHandler(realService)
        );
        proxy.createOrder("MacBook Pro");
    }
}

输出结果

[JDK动态代理] 记录日志:开始执行方法 createOrder
订单创建成功,商品:MacBook Pro
[JDK动态代理] 记录日志:方法执行完成

CGLIB动态代理

通过继承目标类生成子类来实现代理(无需接口)
RealSubject(真实主题)OrderServiceImpl类,实现下单逻辑。(这个增强无需接口)

// 真实对象(无需接口)
public class OrderService {
    public void createOrder(String product) {
        System.out.println("订单创建成功,商品:" + product);
    }
}

Proxy(代理)MethodInterceptor

public class LogMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("[CGLIB代理] 记录日志:开始执行方法 " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("[CGLIB代理] 记录日志:方法执行完成");
        return result;
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class); // 设置父类
        enhancer.setCallback(new LogMethodInterceptor()); // 设置回调
        OrderService proxy = (OrderService) enhancer.create(); // 创建代理对象
        proxy.createOrder("AirPods Pro");
    }
}

输出结果

[CGLIB代理] 记录日志:开始执行方法 createOrder
订单创建成功,商品:AirPods Pro
[CGLIB代理] 记录日志:方法执行完成

三种代理模式对比

代理类型 特点 适用场景
静态代理 手动编写代理类,直接调用目标对象。 代理逻辑简单,目标对象固定。
JDK动态代理 基于接口动态生成代理类,通过反射调用目标方法。 需要代理接口的实现类。
CGLIB代理 通过继承目标类生成子类代理,无需接口。性能较高,但生成代理类较慢。 需要代理没有实现接口的类。

适用场景

  • 静态代理:适合代理逻辑简单且目标对象固定的场景。
  • JDK动态代理:适合基于接口的代理(如Spring AOP默认使用JDK代理)。
  • CGLIB代理:适合代理没有接口的类(如Spring AOP在类没有接口时自动切换为CGLIB)。

二、代理模式在框架源码中的运用

Spring Framework 中的 AOP 代理

Spring AOP 通过代理模式实现方法拦截(如事务管理、日志记录),核心类是 ProxyFactoryBean 和动态代理生成器。

核心角色与类:

  • Subject(抽象主题):被代理的接口或类(如 UserService 接口)。
  • RealSubject(真实主题):实际的目标对象(如 UserServiceImpl 类)。
  • Proxy(代理):由 Spring 动态生成的代理对象,具体分为两类:
  • JDK 动态代理:通过 JdkDynamicAopProxy 类生成(代理接口)。
  • CGLIB 代理:通过 ObjenesisCglibAopProxy 类生成(代理类,无接口时使用)。
// Spring AOP 生成代理的核心逻辑(ProxyFactoryBean)
public class ProxyFactoryBean {
    private Object target;          // RealSubject(真实对象)
    private Class<?>[] interfaces;  // Subject(接口)
    private Advice advice;          // 增强逻辑(如事务、日志)

    public Object getObject() {
        // 根据配置选择生成 JDK 或 CGLIB 代理
        return createAopProxy().getProxy();
    }
}

执行流程:

  1. 客户端调用代理对象的方法(如userService.save())。
  2. 代理对象拦截方法调用,执行增强逻辑(如开启事务)。
  3. 代理对象通过反射调用真实对象的方法(UserServiceImpl.save())。
  4. 返回结果前,执行后置增强逻辑(如提交事务)。

MyBatis 中的 Mapper 接口代理

MyBatis 通过代理模式将 Mapper 接口的方法调用转换为 SQL 执行,核心类是 MapperProxy

核心角色与类

  • Subject(抽象主题)Mapper 接口(如 UserMapper)。
  • RealSubject(真实主题):不存在真实实现类,由代理直接处理逻辑。
  • Proxy(代理)MapperProxy 类,实现 InvocationHandler 接口,动态代理 Mapper 接口。
// MyBatis 的 Mapper 代理生成逻辑(MapperProxyFactory)
public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
}

// MapperProxy 实现 InvocationHandler
public class MapperProxy<T> implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 将方法调用转换为 SQL 执行(如执行 select * from user where id = ?)
        return execute(method, args);
    }
}

执行流程

  1. 客户端调用 UserMapper.findById(1)
  2. MapperProxy 拦截方法调用,解析方法名和参数。
  3. 根据方法名找到对应的 SQL 语句并执行。
  4. 返回数据库查询结果。

三、总结

代理模式的优点

  1. 职责清晰
    • 代理对象负责处理与核心业务无关的逻辑(如权限检查、日志记录、延迟加载等),而真实对象只需关注核心业务逻辑。
    • 符合单一职责原则。
  2. 增强功能
    • 在不修改真实对象的情况下,通过代理对象增强功能(如事务管理、缓存、延迟加载等)。
  3. 解耦
    • 代理模式将客户端与真实对象解耦,客户端只需与代理对象交互,无需直接访问真实对象。
  4. 安全性
    • 代理对象可以控制对真实对象的访问,增加权限检查等安全措施。
  5. 灵活性
    • 动态代理(如 JDK 动态代理、CGLIB 代理)可以在运行时动态生成代理对象,适应不同的需求。

代理模式的缺点

  1. 复杂性增加
    • 引入代理对象会增加系统的复杂性,尤其是动态代理的实现需要理解反射和字节码生成技术。
  2. 性能开销
    • 代理模式可能会引入额外的性能开销,尤其是在频繁调用时(如动态代理的反射调用)。
  3. 代码冗余
    • 静态代理需要为每个真实对象编写代理类,可能导致代码冗余。
  4. 调试困难
    • 动态代理生成的代理类在运行时才存在,调试时可能不如静态代理直观。

代理模式的适用场景

  1. 远程代理
    • 为位于不同地址空间的对象提供本地代表(如 RPC 框架中的远程服务调用)。
  2. 虚拟代理
    • 延迟创建开销较大的对象,直到真正需要时才创建(如图片懒加载、大文件加载)。
  3. 保护代理
    • 控制对敏感对象的访问,基于权限决定是否允许访问(如权限校验)。
  4. 智能引用代理
    • 在访问对象时执行额外操作(如引用计数、懒加载、缓存等)。
  5. AOP(面向切面编程)
    • 在方法调用前后增加通用逻辑(如日志记录、事务管理、性能监控等)。
  6. 延迟初始化
    • 在需要时才初始化对象,节省资源(如 Hibernate 的延迟加载)。
  7. 简化客户端调用
    • 客户端只需与代理对象交互,无需关心真实对象的复杂性(如 MyBatis 的 Mapper 代理)。

网站公告

今日签到

点亮在社区的每一天
去签到