代理模式

130 阅读12分钟

代理模式

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:定义接口
package com.example.staticproxy;

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

    /**
     * 保存用户信息
     */
    boolean saveUser(String userName);
}
步骤2:实现目标类
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:创建代理类
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:使用示例
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.Proxy 和 java.lang.reflect.InvocationHandler
  • 🚫 限制:只能代理实现了接口的类

3.2 实现步骤

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

3.3 代码实现

步骤1:创建 InvocationHandler
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:创建代理工厂
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:使用示例
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:目标类(无需接口)
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:创建方法拦截器
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:创建代理工厂
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:使用示例
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 中的性能测试:

性能测试结果(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动态代理:功能最强大,可以代理任何类,但有一些限制

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