深入解析代理模式:静态代理、JDK 动态代理和 CGLIB 的全方位对比!

代理模式 (Proxy Pattern)是一种结构型设计模式,它提供了对象的替身 ,即代理对象来控制对实际对象 的访问。通过代理对象,可以在不修改目标对象的情况下,扩展控制 其功能。例如,代理模式可以用于延迟加载权限控制日志记录等场景。

🎯 核心要点:

  • 代理对象:代理模式通过代理对象替代实际对象进行控制,代理对象和实际对象实现相同的接口。
  • 控制访问:代理对象可以控制客户端与实际对象的交互,甚至对客户端的请求进行预处理或后处理。
  • 延迟初始化:代理对象可以在需要的时候才创建实际对象,节省资源。

UML类图

Subject :这是接口,定义了代理对象和实际对象都要实现的公共接口,包含方法 request()

RealSubject :实现 Subject 接口的类,表示真正执行操作的对象。

Proxy :同样实现了 Subject 接口,代理 RealSubject 对象,控制对 RealSubject 的访问

静态代理

静态代理是指在编译期就已经确定了代理类。我们必须手动创建代理类,并明确代理哪个对象。代理类与被代理类实现相同的接口,通过代理类来控制对实际对象的访问。

静态代理案例:银行账户管理

假设我们有一个银行账户管理系统,用户通过 BankAccount 类管理账户余额,BankAccountProxy 作为代理类,添加了权限控制功能,只有拥有特定权限的用户才能执行账户操作

案例场景

  • 实际对象BankAccount 负责执行账户的具体操作(如查询余额)。
  • 代理对象BankAccountProxy 负责控制对 BankAccount 的访问,确保只有权限用户可以操作账户。

静态代理代码实现

Step 1: 定义接口
java 复制代码
// Subject 接口
public interface BankAccount {
    void deposit(double amount);
    void withdraw(double amount);
    double getBalance();
}
Step 2: 实现具体的银行账户类
java 复制代码
// RealSubject 实现类
public class RealBankAccount implements BankAccount {
    private double balance;

    public RealBankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    @Override
    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited " + amount + ", new balance is " + balance);
    }

    @Override
    public void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("Withdrew " + amount + ", new balance is " + balance);
        } else {
            System.out.println("Insufficient funds.");
        }
    }

    @Override
    public double getBalance() {
        return balance;
    }
}
Step 3: 实现代理类
java 复制代码
// Proxy 类
public class BankAccountProxy implements BankAccount {
    private RealBankAccount realBankAccount;
    private String userRole;

    public BankAccountProxy(RealBankAccount realBankAccount, String userRole) {
        this.realBankAccount = realBankAccount;
        this.userRole = userRole;
    }

    @Override
    public void deposit(double amount) {
        if (userRole.equals("Admin")) {
            realBankAccount.deposit(amount);
        } else {
            System.out.println("Access denied: You don't have permission to deposit.");
        }
    }

    @Override
    public void withdraw(double amount) {
        if (userRole.equals("Admin")) {
            realBankAccount.withdraw(amount);
        } else {
            System.out.println("Access denied: You don't have permission to withdraw.");
        }
    }

    @Override
    public double getBalance() {
        return realBankAccount.getBalance();
    }
}
Step 4: 测试代理类
java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建真实对象和代理对象
        RealBankAccount realAccount = new RealBankAccount(1000);
        BankAccount proxyAccount = new BankAccountProxy(realAccount, "User");

        // 测试代理访问
        proxyAccount.deposit(500);  // 访问受限
        proxyAccount.withdraw(300); // 访问受限

        // 以 Admin 身份访问
        BankAccount adminAccount = new BankAccountProxy(realAccount, "Admin");
        adminAccount.deposit(500);  // 成功存款
        adminAccount.withdraw(300); // 成功取款
    }
}

输出结果

java 复制代码
Access denied: You don't have permission to deposit.
Access denied: You don't have permission to withdraw.
Deposited 500.0, new balance is 1500.0
Withdrew 300.0, new balance is 1200.0
解释:
  • 权限控制BankAccountProxy 控制了对 RealBankAccount 的访问,只有拥有 Admin 权限的用户才能操作账户。
  • 灵活扩展 :通过代理类,我们可以在不修改 RealBankAccount 的前提下,灵活地添加权限控制功能。

动态代理(JDK 动态代理)

动态代理是在运行时 动态生成代理类,而不是在编译时确定。动态代理可以通过反射机制自动生成代理对象,而无需手动编写代理类。

动态代理案例:银行账户管理(JDK 动态代理)

动态代理 中,代理类是在运行时动态生成的。Java 提供了 java.lang.reflect.Proxy 类和 InvocationHandler 接口来实现动态代理。

案例场景

和静态代理案例类似,我们还是使用 BankAccount 管理账户,但是通过 JDK 动态代理 来动态生成代理类,代理类控制用户的操作权限,并记录日志

动态代理代码实现

Step 1: 定义接口(与静态代理相同)
java 复制代码
// Subject 接口
public interface BankAccount {
    void deposit(double amount);
    void withdraw(double amount);
    double getBalance();
}
Step 2: 实现具体的银行账户类(与静态代理相同)
java 复制代码
// RealSubject 实现类
public class RealBankAccount implements BankAccount {
    private double balance;

    public RealBankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    @Override
    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited " + amount + ", new balance is " + balance);
    }

    @Override
    public void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("Withdrew " + amount + ", new balance is " + balance);
        } else {
            System.out.println("Insufficient funds.");
        }
    }

    @Override
    public double getBalance() {
        return balance;
    }
}
Step 3: 实现 InvocationHandler 接口

InvocationHandler 是动态代理的核心,通过 invoke() 方法拦截对目标对象的方法调用。

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class BankAccountInvocationHandler implements InvocationHandler {
    private Object target;
    private String userRole;

    public BankAccountInvocationHandler(Object target, String userRole) {
        this.target = target;
        this.userRole = userRole;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (userRole.equals("Admin")) {
            System.out.println("Admin access granted.");
            return method.invoke(target, args);  // 调用目标对象的方法
        } else {
            System.out.println("Access denied: You don't have permission to " + method.getName());
            return null;
        }
    }
}
Step 4: 动态代理测试
java 复制代码
import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        RealBankAccount realAccount = new RealBankAccount(1000);

        // 创建动态代理对象
        BankAccount proxyAccount = (BankAccount) Proxy.newProxyInstance(
            realAccount.getClass().getClassLoader(),
            new Class[]{BankAccount.class},
            new BankAccountInvocationHandler(realAccount, "User")
        );

        // 测试代理访问
        proxyAccount.deposit(500);  // 访问受限
        proxyAccount.withdraw(300); // 访问受限

        // 以 Admin 身份访问
        BankAccount adminAccount = (BankAccount) Proxy.newProxyInstance(
            realAccount.getClass().getClassLoader(),
            new Class[]{BankAccount.class},
            new BankAccountInvocationHandler(realAccount, "Admin")
        );

        adminAccount.deposit(500);  // 成功存款
        adminAccount.withdraw(300); // 成功取款
    }
}

输出结果

Access denied: You don't have permission to deposit
Access denied: You don't have permission to withdraw
Admin access granted.
Deposited 500.0, new balance is 1500.0
Admin access granted.
Withdrew 300.0, new balance is 1200.0

解释

  • 运行时生成代理类 :通过 Proxy.newProxyInstance() 方法,动态生成代理类。
  • 权限控制:动态代理可以在运行时灵活地进行权限控制,且不需要手动创建代理类。

CGLIB 动态代理

通过生成目标类的子类,并重写其中的方法来实现代理。它是在运行时生成的字节码,所以可以代理普通类和接口。代理类实际上是目标类的子类,并且会调用父类的方法。

  • 依赖 :需要导入 cglib 相关的库。
  • 限制 :由于 CGLIB 是通过继承实现的,所以不能代理 final 或**final 方法**,因为这些无法被继承和重写。

CGLIB 依赖导入

在项目中,你需要下载CGLIB相关的所有JAR包,或者使用 MavenGradle 导入 cglib 依赖

Jar包下载地址相关JAR点击下载

Maven 依赖

java 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

Gradle 依赖

implementation 'cglib:cglib:3.3.0'

案例场景:银行账户管理(CGLIB 动态代理)

我们将基于前面的银行账户管理系统,使用 CGLIB 实现动态代理,控制用户操作权限并记录日志

场景:

  • 实际对象BankAccount 是一个普通类,没有实现任何接口。
  • 代理对象:使用 CGLIB 动态生成代理类,实现权限控制和日志功能

CGLIB 动态代理代码实现

Step 1: 创建 BankAccount

不再实现接口,这是一个普通类,CGLIB 可以代理这个类。

java 复制代码
// RealSubject 实现类,普通类,没有实现接口
public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited " + amount + ", new balance is " + balance);
    }

    public void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("Withdrew " + amount + ", new balance is " + balance);
        } else {
            System.out.println("Insufficient funds.");
        }
    }

    public double getBalance() {
        return balance;
    }
}
Step 2: 创建 MethodInterceptor 实现类

MethodInterceptor 是 CGLIB 代理的核心,通过重写 intercept() 方法来拦截目标类的方法调用

java 复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class BankAccountMethodInterceptor implements MethodInterceptor {
    private String userRole;

    public BankAccountMethodInterceptor(String userRole) {
        this.userRole = userRole;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        if (userRole.equals("Admin")) {
            System.out.println("Admin access granted.");
            return proxy.invokeSuper(obj, args);  // 调用父类的原方法
        } else {
            System.out.println("Access denied: You don't have permission to " + method.getName());
            return null;
        }
    }
}
Step 3: 使用 Enhancer 动态生成代理类

CGLIB 使用 Enhancer 类来生成代理对象,Enhancer 会生成一个目标类的子类,并将方法调用委托给 MethodInterceptor

java 复制代码
import net.sf.cglib.proxy.Enhancer;

public class Client {
    public static void main(String[] args) {
        // 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(BankAccount.class);  // 设置代理目标类
        enhancer.setCallback(new BankAccountMethodInterceptor("User"));  // 设置拦截器

        // 创建代理对象
        BankAccount proxyAccount = (BankAccount) enhancer.create(new Class[]{double.class}, new Object[]{1000.0});

        // 测试代理访问
        proxyAccount.deposit(500);  // 访问受限
        proxyAccount.withdraw(300); // 访问受限

        // 以 Admin 身份访问
        enhancer.setCallback(new BankAccountMethodInterceptor("Admin"));
        BankAccount adminAccount = (BankAccount) enhancer.create(new Class[]{double.class}, new Object[]{1000.0});

        adminAccount.deposit(500);  // 成功存款
        adminAccount.withdraw(300); // 成功取款
    }
}

输出结果

Access denied: You don't have permission to deposit
Access denied: You don't have permission to withdraw
Admin access granted.
Deposited 500.0, new balance is 1500.0
Admin access granted.
Withdrew 300.0, new balance is 1200.0

解释

  • 动态生成代理类 :通过 Enhancer 类,动态生成了 BankAccount 类的代理对象。
  • 权限控制MethodInterceptor 控制了对目标方法的调用,只有具有 Admin 权限的用户才能执行操作。
  • 日志功能:代理类在执行目标方法前,打印日志信息。

CGLIB 动态代理的优缺点

优点

  1. 支持无接口类的代理:CGLIB 能够代理普通类,不要求目标类必须实现接口,这比 JDK 动态代理更灵活。
  2. 性能高:相比 JDK 动态代理,CGLIB 生成的代理类性能更高,尤其是在大量调用代理方法的场景下。
  3. 透明性:客户端无需修改,代理类的生成是透明的。

缺点

  1. 不能代理 final 类和方法 :由于 CGLIB 代理是通过生成子类实现的,因此无法代理 final 类和 final 方法。
  2. 依赖第三方库:CGLIB 是一个外部库,增加了项目的依赖复杂度。

总结:CGLIB 动态代理的特点

  • 代理普通类:CGLIB 允许代理没有实现接口的类,这比 JDK 动态代理更加灵活。
  • 通过继承实现代理:CGLIB 生成目标类的子类,并重写目标方法来实现代理。
  • 应用场景广泛:CGLIB 动态代理适用于需要代理普通类、且调用频繁的场景。

🎯 Spring AOP 代理机制

Spring AOP (Aspect-Oriented Programming,面向切面编程)是 Spring 框架中的核心特性之一,它通过代理对象来对目标对象的方法进行拦截,在方法执行前后加入额外的逻辑,如日志记录、权限验证、事务管理等。

Spring AOP 中使用了两种代理机制:

  1. JDK 动态代理(基于接口的代理)
  2. CGLIB 动态代理(基于子类的代理)

使用的代理类型

  • JDK 动态代理 :当目标类实现了接口时,Spring AOP 默认使用 JDK 动态代理来生成代理对象。
  • CGLIB 动态代理 :当目标类没有实现接口时,Spring AOP 会使用 CGLIB 来生成代理对象。

Spring AOP 如何选择代理机制

Spring 选择代理的规则
  1. 如果目标对象实现了接口 ,Spring AOP 默认使用 JDK 动态代理
  2. 如果目标对象没有实现接口 ,Spring AOP 使用 CGLIB 动态代理
  3. 可以强制使用 CGLIB:即使目标对象实现了接口,也可以通过配置来强制使用 CGLIB 代理。

Spring AOP 动态代理应用场景

Spring AOP 的代理机制广泛应用于以下场景:

  1. 事务管理:通过代理对象,在方法执行时自动管理事务的开启和提交。
  2. 日志记录:在方法执行前后自动添加日志记录。
  3. 权限控制:通过代理对象,控制用户是否有权限调用某些方法。
  4. 缓存机制:在方法执行前,先检查缓存,如果缓存中存在结果则直接返回,否则执行目标方法并将结果存入缓存

三种代理类型的对比

下表详细对比了 静态代理JDK 动态代理CGLIB 动态代理 的不同点。

对比维度 静态代理 JDK 动态代理 CGLIB 动态代理
实现方式 手动创建代理类 通过 Proxy 类和 InvocationHandler 动态生成 通过继承目标类,使用字节码生成子类
是否需要接口 是,需要代理类和目标类实现相同接口 是,必须代理实现了接口的类 否,不需要接口,直接代理类本身
代理类生成时间 编译时生成,代码已确定 运行时动态生成 运行时动态生成
实现复杂度 需要手动编写代理类,代码重复 较简单,自动生成代理类 复杂度较高,需要字节码生成库
方法调用方式 代理类直接调用目标类方法 代理对象通过 InvocationHandler 反射调用目标方法 通过字节码技术生成子类,直接调用父类方法
代理性能 性能较好,方法直接调用 性能较差,基于反射调用,反射开销大 性能较高,生成的子类直接调用父类方法
代理对象结构 代理对象和目标对象有相同的接口 代理对象和目标对象实现相同接口 代理对象是目标类的子类
应用场景 适用于代理数量少、简单的场景 适用于需要代理实现接口的场景 适用于没有实现接口的类,或者需要大量代理的场景
是否可代理 final 否,无法代理 final 否,无法代理 final
优点 实现简单,直观 灵活,可以代理接口,易于扩展 可代理普通类,性能较高,适用于没有接口的类
缺点 需要为每个目标类手动编写代理类,代码冗余 只能代理接口,基于反射调用性能较低 无法代理 final 类,依赖外部库,配置较复杂

总结:三种代理的特点和适用场景

  1. 静态代理
    • 特点:代理类由开发者手动编写,固定且已确定,代码较多、可维护性较低。
    • 适用场景:代理类少、功能固定的场景,简单、容易实现。
  2. JDK 动态代理
    • 特点:只能代理实现了接口的类,通过反射调用目标方法,代理类在运行时生成,性能相对较低。
    • 适用场景:目标对象实现了接口,尤其是需要动态代理多个接口的场景,如日志记录、权限控制等。
  3. CGLIB 动态代理
    • 特点 :不要求目标类必须实现接口,使用字节码生成技术生成目标类的子类,性能高于 JDK 动态代理,但无法代理 final 类和 final 方法。
    • 适用场景:目标类没有实现接口,且代理调用频繁时使用,尤其适合对普通类的代理
相关推荐
LG.YDX5 分钟前
java:练习
java
给自己做减法19 分钟前
排序算法快速记忆
java·算法·排序算法
计算机学姐40 分钟前
基于微信小程序的食堂点餐预约管理系统
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
骆晨学长1 小时前
基于springboot学生健康管理系统的设计与实现
java·开发语言·spring boot·后端·spring
骆晨学长1 小时前
基于Springboot的医疗健康助手开题报告
java·spring boot·后端
二十雨辰1 小时前
[苍穹外卖]-09Spring Task定时任务
java·数据库·spring
我是小酒1 小时前
掌握 Spring:从新手到高手的常见问题汇总
java·后端·spring·springboot
A懿轩A1 小时前
MySQL SQL多表查询语句各种连接
java·开发语言·数据库·sql·mysql·mybatis
代码代码快快显灵1 小时前
XML标记语言
xml·java·数据库
DKPT1 小时前
数据结构之排序的基本概念
java·数据结构·笔记·学习·算法