代理模式

代理模式

1. 代理模式概述

1.1 什么是代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。

核心思想:用户不能直接使用目标对象,而是构造出一个代理对象,由代理对象作为中转,代理对象负责调用目标对象真正的行为,从而把结果返回给用户。

1.2 生活中的代理例子

就像明星和经纪人的关系:

  • 静态代理:明星亲自挑选固定的经纪人,关系确定且稳定
  • 动态代理:明星委托给代理公司,根据不同需求动态分配不同的经纪人

如上图所示,用户不能直接使用目标对象,而是构造出一个代理对象,由代理对象作为中转,代理对象负责调用目标对象真正的行为,从而把结果返回给用户。

也就是说,代理的关键点就是代理对象和目标对象的关系

代理其实就和经纪人一样,比如你是一个明星,有很多粉丝。你的流量很多,经常会有很多金主来找你洽谈合作等,你自己肯定忙不过来,因为你要处理的不只是谈合作这件事情,你还要懂才艺、拍戏、维护和粉丝的关系、营销等。为此,你找了一个经纪人,你让他负责和金主谈合作这件事,经纪人做事很认真负责,它圆满的完成了任务,于是,金主找你谈合作就变成了金主和你的经纪人谈合作,你就有更多的时间来忙其他事情了。如下图所示

这是一种静态代理,因为这个代理(经纪人)是你自己亲自挑选的。

但是后来随着你的业务逐渐拓展,你无法选择每个经纪人,所以你索性交给了代理公司来帮你做。如果你想在 B 站火一把,那就直接让代理公司帮你找到负责营销方面的代理人,如果你想维护和粉丝的关系,那你直接让代理公司给你找一些托儿就可以了,那么此时的关系图会变为如下

此时你几乎所有的工作都是由代理公司来进行打理,而他们派出谁来帮你做这些事情你就不得而知了,这得根据实际情况来定,因为代理公司也不只是负责你一个明星,而且每个人所擅长的领域也不同,所以你只有等到有实际需求后,才会给你指定对应的代理人,这种情况就叫做动态代理

原文链接:juejin.cn/post/691154...

1.3 代理模式的角色

  1. 抽象主题(Subject):定义了真实主题和代理主题的共同接口
  2. 真实主题(RealSubject):定义了代理所代表的真实对象
  3. 代理(Proxy):保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口

2. 静态代理

2.1 静态代理介绍

静态代理是在编译时就确定代理关系的代理模式。需要手动编写代理类,代理类和目标类实现相同的接口。

2.2 实现步骤

  1. 定义抽象主题接口
  2. 创建真实主题类实现接口
  3. 创建代理类实现相同接口
  4. 在代理类中持有真实主题的引用
  5. 在代理方法中添加额外逻辑并调用真实主题方法

2.3 代码实现

步骤1:定义接口
java 复制代码
package com.example.staticproxy;

/**
 * 用户服务接口
 */
public interface UserService {
    /**
     * 根据用户ID查询用户信息
     */
    String getUserById(Long userId);

    /**
     * 保存用户信息
     */
    boolean saveUser(String userName);
}
步骤2:实现目标类
java 复制代码
package com.example.staticproxy;

/**
 * 用户服务实现类(被代理的目标对象)
 */
public class UserServiceImpl implements UserService {

    @Override
    public String getUserById(Long userId) {
        // 模拟数据库查询
        System.out.println("正在查询用户ID: " + userId);
        return "用户" + userId + "的信息";
    }

    @Override
    public boolean saveUser(String userName) {
        // 模拟保存用户
        System.out.println("正在保存用户: " + userName);
        return true;
    }
}
步骤3:创建代理类
java 复制代码
package com.example.staticproxy;

import java.util.Date;

/**
 * 用户服务静态代理类
 * 在不修改原有代码的基础上,增加额外功能(如日志、权限检查等)
 */
public class UserServiceProxy implements UserService {

    private UserService target; // 被代理的目标对象

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

    @Override
    public String getUserById(Long userId) {
        before("getUserById", userId);
        String result = target.getUserById(userId); // 实际调用真实主题角色的方法
        after("getUserById", result);
        return result;
    }

    @Override
    public boolean saveUser(String userName) {
        before("saveUser", userName);
        boolean result = target.saveUser(userName); // 实际调用真实主题角色的方法
        after("saveUser", result);
        return result;
    }

    /**
     * 前置处理:在执行方法之前执行
     */
    private void before(String methodName, Object... args) {
        System.out.println(String.format("【staticproxy】log start time [%s] - 执行方法: %s, 参数: %s",
            new Date(), methodName, java.util.Arrays.toString(args)));
    }

    /**
     * 后置处理:在执行方法之后执行
     */
    private void after(String methodName, Object result) {
        System.out.println(String.format("【staticproxy】log end time [%s] - 方法: %s, 返回结果: %s",
            new Date(), methodName, result));
    }
}
步骤4:使用示例
java 复制代码
public class StaticProxyDemo {
    public static void main(String[] args) {
        // 1. 创建目标对象
        UserService userService = new UserServiceImpl();

        // 2. 创建代理对象,将目标对象传入
        UserService proxy = new UserServiceProxy(userService);

        // 3. 通过代理对象调用方法
        String user = proxy.getUserById(1001L);
        boolean success = proxy.saveUser("张三");
    }
}

2.4 静态代理特点

✅ 优点:

  • 性能最好,没有反射开销
  • 代码清晰,易于理解和调试
  • 编译时确定,类型安全

❌ 缺点:

  • 代码冗余,每个接口都需要写一个代理类
  • 维护困难,接口变化时代理类也要修改
  • 灵活性差,代理关系在编译时就确定

🎯 适用场景:

  • 代理关系固定,不需要动态变化
  • 对性能要求极高的场景
  • 需要在编译时确定代理关系

3. JDK 动态代理

3.1 JDK 动态代理介绍

  • JDK自带 - 从JDK 1.3开始就内置在Java标准库中
  • 📦 包路径java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler
  • 🚫 限制:只能代理实现了接口的类

3.2 实现步骤

  1. 目标类必须实现接口
  2. 创建 InvocationHandler 实现类
  3. 使用 Proxy.newProxyInstance() 创建代理对象
  4. 通过代理对象调用方法

3.3 代码实现

步骤1:创建 InvocationHandler
java 复制代码
package com.example.jdkproxy;

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

/**
 * JDK动态代理处理器
 * 实现InvocationHandler接口,定义代理对象的行为
 */
public class JdkProxyHandler implements InvocationHandler {

    private Object target; // 被代理的目标对象

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

    /**
     * 代理对象的所有方法调用都会转发到这个方法
     * @param proxy 代理对象本身
     * @param method 被调用的方法
     * @param args 方法参数
     * @return 方法执行结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(method.getName(), args);
        Object result = method.invoke(target, args); // 执行目标方法
        after(method.getName(), result);
        return result;
    }

    /**
     * 前置处理:在执行方法之前执行
     */
    private void before(String methodName, Object... args) {
        System.out.println(String.format("【jdkproxy】log start time [%s] - 执行方法: %s, 参数: %s",
            new java.util.Date(), methodName, java.util.Arrays.toString(args)));
    }

    /**
     * 后置处理:在执行方法之后执行
     */
    private void after(String methodName, Object result) {
        System.out.println(String.format("【jdkproxy】log end time [%s] - 方法: %s, 返回结果: %s",
            new java.util.Date(), methodName, result));
    }
}
步骤2:创建代理工厂
java 复制代码
package com.example.jdkproxy;

import java.lang.reflect.Proxy;

/**
 * JDK动态代理工厂
 * 用于创建动态代理对象
 */
public class JdkProxyFactory {

    /**
     * 创建代理对象
     * @param target 被代理的目标对象
     * @return 代理对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        // 获取目标对象的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();

        // 获取目标对象实现的所有接口
        Class<?>[] interfaces = target.getClass().getInterfaces();

        // 创建InvocationHandler
        JdkProxyHandler handler = new JdkProxyHandler(target);

        // 使用Proxy.newProxyInstance创建代理对象
        return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}
步骤3:使用示例
java 复制代码
public class JdkProxyDemo {
    public static void main(String[] args) {
        // 1. 创建目标对象
        UserService userService = new UserServiceImpl();

        // 2. 使用工厂创建代理对象
        UserService proxy = JdkProxyFactory.createProxy(userService);

        // 3. 查看代理对象的类信息
        System.out.println("目标对象类型: " + userService.getClass().getName());
        System.out.println("代理对象类型: " + proxy.getClass().getName());
        System.out.println("代理对象是否是Proxy的实例: " + (proxy instanceof java.lang.reflect.Proxy));

        // 4. 通过代理对象调用方法
        String user = proxy.getUserById(2001L);
        boolean success = proxy.saveUser("李四");
    }
}

3.4 JDK 动态代理特点

✅ 优点:

  • 不需要手动编写代理类
  • 一个 InvocationHandler 可以代理多个接口
  • 灵活性高,运行时动态生成
  • JDK 原生支持,无需额外依赖

❌ 缺点:

  • 只能代理实现了接口的类
  • 性能比静态代理差(反射调用)
  • 调试相对困难

🎯 适用场景:

  • 目标对象实现了接口
  • 需要代理多个不同的类
  • Spring AOP 的默认选择
  • 需要运行时动态决定代理行为

4. CGLIB 动态代理

4.1 CGLIB(Code Generation Library)

  • 不是JDK自带 - 需要额外添加依赖
  • 📝 全称Code Generation Library(代码生成库)
  • 🏢 开发者:最初由Apache开发,现在主要由Spring团队维护
  • 原理:通过字节码技术动态生成子类来实现代理。代理类继承于目标类,每次调用代理类的方法都会在拦截器中进行拦截,拦截器中再调用目标类的方法。

注:由于JDK动态代理只能代理实现了接口的类,故在 Springboot2.x 版本都是使用CGLIB动态代理。

4.2 实现步骤

  1. 添加 CGLIB 依赖
  2. 创建 MethodInterceptor 实现类
  3. 使用 Enhancer 创建代理对象
  4. 通过代理对象调用方法

4.3 代码实现

步骤1:目标类(无需接口)
java 复制代码
package com.example.cglibproxy;

/**
 * 产品服务类(没有接口,用于演示CGLIB代理)
 */
public class ProductService {

    /**
     * 根据产品ID查询产品信息
     */
    public String getProductById(Long productId) {
        System.out.println("正在查询产品ID: " + productId);
        return "产品" + productId + "的详细信息";
    }

    /**
     * 添加新产品
     */
    public boolean addProduct(String productName, double price) {
        System.out.println("正在添加产品: " + productName + ", 价格: " + price);
        return true;
    }

    /**
     * final方法,无法被代理
     */
    public final String getFinalMethod() {
        return "这是final方法,CGLIB无法代理";
    }
}
步骤2:创建方法拦截器
java 复制代码
package com.example.cglibproxy;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * CGLIB代理拦截器
 * 实现MethodInterceptor接口,定义代理对象的行为
 */
public class CglibProxyInterceptor implements MethodInterceptor {

    /**
     * 拦截目标方法的执行
     * @param obj 代理对象
     * @param method 被拦截的方法
     * @param args 方法参数
     * @param proxy 用于调用原始方法的代理
     * @return 方法执行结果
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before(method.getName(), args);
        // 调用原始方法
        // 注意:这里使用proxy.invokeSuper而不是method.invoke
        // proxy.invokeSuper调用的是父类(原始类)的方法
        Object result = proxy.invokeSuper(obj, args);
        after(method.getName(), result);
        return result;
    }

    /**
     * 前置处理:在执行方法之前执行
     */
    private void before(String methodName, Object... args) {
        System.out.println(String.format("【cglibproxy】log start time [%s] - 执行方法: %s, 参数: %s",
            new java.util.Date(), methodName, java.util.Arrays.toString(args)));
    }

    /**
     * 后置处理:在执行方法之后执行
     */
    private void after(String methodName, Object result) {
        System.out.println(String.format("【cglibproxy】log end time [%s] - 方法: %s, 返回结果: %s",
            new java.util.Date(), methodName, result));
    }
}
步骤3:创建代理工厂
java 复制代码
package com.example.cglibproxy;

import net.sf.cglib.proxy.Enhancer;

/**
 * CGLIB代理工厂
 * 用于创建CGLIB动态代理对象
 */
public class CglibProxyFactory {

    /**
     * 创建CGLIB代理对象
     * @param targetClass 目标类的Class对象
     * @return 代理对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(Class<T> targetClass) {
        // 创建Enhancer对象,用于生成代理类
        Enhancer enhancer = new Enhancer();

        // 设置父类(被代理的类)
        enhancer.setSuperclass(targetClass);

        // 设置回调函数(拦截器)
        enhancer.setCallback(new CglibProxyInterceptor());

        // 创建代理对象
        return (T) enhancer.create();
    }
}
步骤4:使用示例
java 复制代码
public class CglibProxyDemo {
    public static void main(String[] args) {
        // 1. 创建代理对象(注意:传入的是Class对象,不是实例)
        ProductService proxy = CglibProxyFactory.createProxy(ProductService.class);

        // 2. 查看代理对象的类信息
        System.out.println("代理对象类型: " + proxy.getClass().getSimpleName());
        System.out.println("父类: " + proxy.getClass().getSuperclass().getSimpleName());

        // 3. 通过代理对象调用方法
        String product = proxy.getProductById(3001L);
        boolean success = proxy.addProduct("iPhone 15", 7999.0);

        // 4. 演示final方法无法被代理
        String finalResult = proxy.getFinalMethod();
        System.out.println("final方法结果: " + finalResult);
        System.out.println("注意:final方法没有被代理拦截!");
    }
}

4.4 CGLIB 动态代理特点

✅ 优点:

  • 可以代理没有接口的类
  • 性能比JDK动态代理好(直接方法调用)
  • 功能强大,支持多种代理模式
  • 可以拦截所有public和protected方法

❌ 缺点:

  • 需要额外的CGLIB依赖
  • 无法代理final类和final方法
  • 无法代理private方法
  • 创建代理对象的开销较大

🎯 适用场景:

  • 目标对象没有实现接口
  • 需要代理第三方类库中的类
  • Spring AOP中的类代理
  • 需要拦截所有方法调用的场景

5. 三种代理模式对比

5.1 对比表格

特性 静态代理 JDK动态代理 CGLIB动态代理
实现方式 手动编写代理 基于接口 基于继承
代理对象 必须有接口 必须有接口 任何类
生成时机 编译时 运行时 运行时
性能 最好 中等 较好
灵活性
代码维护 繁琐 简单 简单
依赖 JDK原生 需要CGLIB
限制 代码冗余 必须有接口 无法代理final

5.2 性能对比

根据 proxy-demo 中的性能测试:

makefile 复制代码
性能测试结果(100000次调用):
原始调用: 2ms
静态代理: 5ms (慢了 3ms)
JDK动态代理: 45ms (慢了 43ms)
CGLIB代理: 12ms (慢了 10ms)

性能排序:静态代理 > CGLIB代理 > JDK动态代理

5.3 使用建议

  • 静态代理:适用于代理关系固定、对性能要求极高的场景
  • JDK动态代理:适用于有接口的类,Spring AOP默认选择
  • CGLIB代理:适用于没有接口的类,功能更强大

5.4 注意事项

  • JDK动态代理只能代理实现了接口的类
  • CGLIB代理无法代理final类和final方法
  • 性能方面:静态代理 > CGLIB代理 > JDK动态代理
  • Java 9+环境下CGLIB需要额外的JVM参数支持

6. 实际应用场景

6.1 Spring AOP

  • JDK动态代理:当目标对象实现了接口时使用
  • CGLIB代理:当目标对象没有实现接口时使用

6.2 RPC框架

  • 客户端代理:将远程调用伪装成本地调用
  • 服务端代理:统一处理请求和响应

6.3 缓存代理

  • 在方法调用前检查缓存
  • 缓存未命中时调用真实方法并缓存结果

6.4 权限控制

  • 在方法调用前进行权限检查
  • 无权限时拒绝访问

6.5 日志记录

  • 记录方法调用的参数和返回值
  • 统计方法执行时间

7. 总结

代理模式是一种非常实用的设计模式,它在不修改原有代码的基础上,为对象提供额外的功能。三种代理方式各有特点:

  1. 静态代理:简单直接,性能最好,但缺乏灵活性
  2. JDK动态代理:基于接口,灵活性高,Spring AOP的默认选择
  3. CGLIB动态代理:功能最强大,可以代理任何类,但有一些限制

抽象思维:创建目标对象 - 创建代理对象 - 通过代理对象调用方法

相关推荐
考虑考虑5 小时前
Jpa中的枚举类型
spring boot·后端·spring
peter5275 小时前
LangChain4j入门使用
后端
ArabySide5 小时前
【ASP.NET Core】分布式场景下ASP.NET Core中JWT应用教程
分布式·后端·asp.net core
hui函数6 小时前
Python全栈(基础篇)——Day06:后端内容(定义函数+调用函数+实战演示+每日一题)
后端·python
bcbnb6 小时前
Charles隐藏功能全攻略,10个高效技巧让你的抓包调试更专业
后端
用户4099322502126 小时前
PostgreSQL选Join策略有啥小九九?Nested Loop/Merge/Hash谁是它的菜?
后端·ai编程·trae
koddnty6 小时前
协程退出与智能指针
后端·架构
用户6120414922136 小时前
C语言做的物联网设备数据采集模拟器
c语言·后端·敏捷开发
华仔啊7 小时前
千万级大表如何新增字段?别再直接 ALTER 了
后端·mysql