当前位置: 首页 > news >正文

Spring AOP 核心的技术之一:动态代理

什么是动态代理?

动态代理,是一种在运行时动态创建代理对象的技术。这个代理对象可以在不修改原始类源代码的情况下,拦截对原始对象方法的调用,并在调用前后插入自定义的逻辑。

关于Spring AOP

Spring AOP 就是动态代理的一个典型应用。当我们定义一个切面 (Aspect) 和切点 (Pointcut) 后,Spring 容器会在启动时或者运行时,为匹配切点的 Bean 创建一个代理对象。
这个代理对象会包装(Wrap)原始的 Bean。当我们调用这个 Bean 的方法时,实际上是先调用了代理对象的方法。代理对象内部会根据 AOP 的配置(比如 @Before, @Around, @After 通知),先执行相应的增强逻辑,然后再(或者在 @Around 通知里决定是否)去调用原始 Bean 的目标方法。
Spring 主要使用两种动态代理技术:如果是基于接口的代理,默认使用 JDK 动态代理(需要目标类实现接口);如果是基于类的代理,则使用 CGLIB(通过生成子类的方式)。

关于JDK 动态代理和CGLIB

先通过代码写一个简单的 Demo 来理解他们
** 我们需要一个被代理的目标:**

// 1. 目标接口
public interface UserService {
    String addUser(String username);
    void deleteUser(String username);
}

// 2. 目标实现类 (这个类可以被 JDK 和 CGLIB 代理)
public class UserServiceImpl implements UserService {
    @Override
    public String addUser(String username) {
        System.out.println("UserServiceImpl [数据库]:添加用户 " + username);
        return "添加成功: " + username;
    }

    @Override
    public void deleteUser(String username) {
         System.out.println("UserServiceImpl [数据库]:删除用户 " + username);
    }

    // CGLIB 可以代理非接口方法 (如果类不是final)
    public void nonInterfaceMethod() {
        System.out.println("UserServiceImpl: 这是一个实现类自己定义的方法。");
    }
}

JDK 动态代理:

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

// 3. JDK 代理的 InvocationHandler
class JdkInvocationHandler implements InvocationHandler {

    // 目标对象
    private final Object target;

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

    /**
     * 当通过代理对象调用方法时,这个 invoke 方法会被执行
     * @param proxy 代理对象本身 (一般很少使用)
     * @param method 被调用的目标方法 (例如 addUser 方法的 Method 对象)
     * @param args 调用目标方法时传入的参数 (例如 "张三")
     * @return 目标方法的返回值
     * @throws Throwable 目标方法执行可能抛出的异常
     */
    @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 代理] === 方法 [" + method.getName() + "] 执行后 ===");
        System.out.println("[JDK 代理] 返回结果: " + result); // 注意: deleteUser 没有返回值,这里会是 null

        // 可以对结果进行处理再返回
        if (result instanceof String) {
           return ((String) result).toUpperCase(); // 示例:将返回的字符串转大写
        }
        return result;
    }
}

// 4. JDK 代理测试类
public class JdkProxyDemo {
    public static void main(String[] args) {
        // 1. 创建目标对象实例
        UserService target = new UserServiceImpl();
        System.out.println("目标对象类名: " + target.getClass().getName()); // 输出原始类名

        // 2. 创建 InvocationHandler 实例
        InvocationHandler handler = new JdkInvocationHandler(target);

        // 3. 使用 Proxy.newProxyInstance 创建代理对象
        // 参数1: 目标对象的类加载器
        // 参数2: 目标对象实现的接口数组 (代理对象会实现这些接口)
        // 参数3: InvocationHandler 实例
        UserService proxyInstance = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), // 关键:必须基于接口
                handler
        );

        System.out.println("代理对象类名: " + proxyInstance.getClass().getName()); // 输出代理类名,类似 com.sun.proxy.$Proxy0

        // 4. 通过代理对象调用方法
        System.out.println("\n------ 调用 addUser ------");
        String addUserResult = proxyInstance.addUser("张三");
        System.out.println("最终调用者收到的结果: " + addUserResult);


        System.out.println("\n------ 调用 deleteUser ------");
        proxyInstance.deleteUser("李四");

        // 注意:JDK 代理无法直接调用目标实现类中定义的非接口方法
        // 下面这行会报错,因为代理对象只实现了 UserService 接口
        proxyInstance.nonInterfaceMethod(); // 编译错误
        // 如果非要调用,需要强转成原始类型,但这样就失去了代理的意义
        ((UserServiceImpl)proxyInstance).nonInterfaceMethod(); // 运行时会抛 ClassCastException,因为代理对象不是 UserServiceImpl 类型
    }
}

CGLIB 动态代理:
CGLIB 需要引入依赖。如果是 Maven 项目,在 pom.xml 中添加:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

CGLIB 需要一个 MethodInterceptor

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 5. CGLIB 的 MethodInterceptor
class CglibMethodInterceptor implements MethodInterceptor {

    // CGLIB 不需要显式持有目标对象,因为它是通过创建子类来实现的

    /**
     * 拦截 CGLIB 生成的代理类(子类)的方法调用
     * @param obj CGLIB 生成的代理对象 (子类实例)
     * @param method 被拦截的父类方法 (例如 UserServiceImpl 的 addUser 方法)
     * @param args 调用方法的参数
     * @param methodProxy 用于调用父类原始方法的代理,效率比反射高
     * @return 原始方法的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("[CGLIB 代理] === 方法 [" + method.getName() + "] 执行前 ===");

        // 通过 MethodProxy 调用原始父类的方法
        // 注意:这里是 invokeSuper,不是 invoke
        Object result = methodProxy.invokeSuper(obj, args);

        System.out.println("[CGLIB 代理] === 方法 [" + method.getName() + "] 执行后 ===");
        System.out.println("[CGLIB 代理] 返回结果: " + result);

        // 同样可以处理结果
         if (result instanceof String) {
           return ((String) result).toUpperCase() + " (From CGLIB)"; // 示例:加点区别
        }
        return result;
    }
}

// 6. CGLIB 代理测试类
public class CglibProxyDemo {
    public static void main(String[] args) {
        // 1. 创建 CGLIB 的 Enhancer 对象
        Enhancer enhancer = new Enhancer();

        // 2. 设置需要被代理的父类
        enhancer.setSuperclass(UserServiceImpl.class); // 关键:基于类

        // 3. 设置回调拦截器
        enhancer.setCallback(new CglibMethodInterceptor());

        // 4. 创建代理对象 (实际上是 UserServiceImpl 的子类实例)
        UserServiceImpl proxyInstance = (UserServiceImpl) enhancer.create();

        System.out.println("代理对象类名: " + proxyInstance.getClass().getName()); // 输出代理类名,类似 xxx.UserServiceImpl$$EnhancerByCGLIB$$...

        // 5. 通过代理对象调用方法
        System.out.println("\n------ 调用 addUser ------");
        String addUserResult = proxyInstance.addUser("王五");
        System.out.println("最终调用者收到的结果: " + addUserResult);

        System.out.println("\n------ 调用 deleteUser ------");
        proxyInstance.deleteUser("赵六");

        // CGLIB 可以代理父类中非 final 的 public/protected 方法,即使它不是接口方法
        System.out.println("\n------ 调用 nonInterfaceMethod ------");
        proxyInstance.nonInterfaceMethod();

        // 注意:如果 UserServiceImpl 是 final 类,或者 addUser 是 final 方法,CGLIB 会失败
    }
}

说下 CGLIB 动态代理与 JDK 动态代理的区别

特性JDK 动态代理CGLIB 动态代理
实现原理基于接口实现基于继承目标类 (创建子类)
依赖Java SDK 自带,无需额外依赖需要引入第三方 cglib
代理对象类型实现目标对象相同接口的新类实例目标对象的子类实例
目标类要求目标类必须实现至少一个接口目标类无需实现接口,但不能是 final
代理方法范围只能代理接口中定义的方法可以代理目标类中所有非 final 的方法
拦截器java.lang.reflect.InvocationHandler net.sf.cglib.proxy.MethodInterceptor
调用原始方法使用反射 Method.invoke(target, args)使用 MethodProxy.invokeSuper(proxy, args)
性能 (历史观点)创建代理稍慢,方法调用稍快(但反射调用有开销)销) 创建代理稍快,方法调用稍慢 (但 invokeSuper 通常比反射快)
性能 (现代观点)差异通常很小,可以忽略不计。invokeSuper比反射invoke通常性能更好。
Spring AOP目标类实现接口时默认使用目标类未实现接口时使用,或强制配置时使用

简单的来说就是:

  • JDK 代理和被代理的目标类是兄弟关系(都实现了同一个接口)。
  • CGLIB 代理和被代理的目标类是父子关系(代理类继承了目标类)。

最后

如果目标类实现了接口 Spring AOP 默认使用 JDK 动态代理。
原因: 这是 Java 标准库提供的代理方式,并且通常认为基于接口的代理是更优的面向对象实践。Spring 会创建一个实现了目标类所实现的所有接口的新代理类。这个代理类持有对原始目标对象的引用,并通过 InvocationHandler 机制将增强逻辑(AOP Advice)织入。
结果: 你得到的代理 Bean 可以被赋值给它所实现的任何接口类型,但不能直接赋值给原始的目标实现类类型(除非强制类型转换,但通常不推荐,且可能失败)。

如果目标类没有实现任何接口 那么只能使用 CGLIB 代理。
原因: JDK 动态代理要求必须有接口,既然目标类没有实现接口,JDK 代理就无法工作。Spring 别无选择,只能借助 CGLIB 库。CGLIB 通过创建目标类的子类来作为代理。它会覆写父类(目标类)中的非 final 方法,并在这些覆写的方法中加入增强逻辑,然后调用父类的原始方法。
结果: 你得到的代理 Bean 是目标实现类的一个子类。因此,它可以被赋值给原始的目标实现类类型。
Spring 提供了一个配置选项,允许你强制 Spring AOP 使用 CGLIB 代理,即使目标类实现了接口。这个配置就是 proxy-target-class 属性。
可以通过配置更改默认动态代理的实现
Spring 提供了一个配置选项,允许你强制 Spring AOP 使用 CGLIB 代理,即使目标类实现了接口。这个配置就是 proxy-target-class 属性。

  • proxy-target-class = false (默认值 - 在非 Spring Boot 或较早的 Spring Boot 版本中):
    • 遵循上述的基本规则:实现接口用 JDK,没实现接口用 CGLIB。
  • proxy-target-class = true:
    • 强制 Spring AOP 统一使用 CGLIB 代理,无论目标类是否实现了接口。
    • 只要目标类不是 final 的,并且需要被代理的方法不是 final 的,Spring 就会为它创建基于类的 CGLIB 代理。

实例:

 # application.yml
spring:
  aop:
    proxy-target-class: true

随便聊聊

除了 AOP,动态代理在 Spring 框架的其他模块中也有广泛应用。比如,我们常用的 @Transactional 注解,它的事务开启、提交、回滚逻辑就是通过动态代理织入到业务方法调用的前后。还有像 Spring Cloud Feign,它能让我们像调用本地接口一样调用远程 HTTP 服务,其底层也是为我们定义的 Feign 接口生成了动态代理实现类,由代理类负责网络通信和序列化。
注意:
CGLIB 无法代理目标类中 final 或 private 的方法(因为 final 方法不能被子类覆写,private 方法对子类不可见)。这也是@Transactional如果应用于非 public 方法时会导致事务失效的一个原因

相关文章:

  • 其他合成方式介绍
  • nacos集群部署
  • 【redis】summary
  • rust 同时处理多个异步任务,并在一个任务完成退出
  • PythonJSON解析如何优雅处理嵌套JSON字符串
  • springboot中使用async实现异步编程
  • Docker Compose 部署Nginx反向代理 tomcat
  • 每日算法-250407
  • 数字经济产业标杆:树莓集团如何塑造产业服务价值体系
  • 没有独立显卡如何安装torch
  • 极简设计的力量:用 `apiStore` 提升项目效率与稳定性
  • oracle查询是否锁表了
  • Objective-C语言的编程范式
  • 昇腾910b多机部署deepseek-r1-w8a8量化全攻略
  • Hive 常见面试 300 问
  • leetcode 368. 最大整除子集 中等
  • Scala(六)
  • Matlab绘图—‘‘错误使用 plot输入参数的数目不足‘‘
  • 工程项目中通讯协议常见问题
  • 零代码构建AI知识库:基于亮数据网页抓取API的维基百科数据自动化采集实战
  • 上海老字号卖黄金,与动漫IP联名两周销售额近亿元
  • 人形机器人灵犀X2掌握新技能:有了“内心戏”,还会拳脚功夫
  • 一涉嫌开设赌场的网上在逃人员在山东威海落网
  • 一船明月过沧州:为何这座城敢称“文武双全”?
  • 最高法、证监会:常态化开展证券纠纷代表人诉讼,降低投资者维权成本
  • 上海锦江乐园摩天轮正在拆除中,预计5月底6月初拆完