Spring Boot 项目中,JDK 动态代理和 CGLIB 动态代理的使用

news/2025/2/26 21:55:19

在 Spring Boot 项目中,JDK 动态代理和 CGLIB 动态代理都是实现 AOP (面向切面编程) 的重要技术。 它们的主要区别在于代理对象的生成方式和适用范围。 下面详细介绍它们的使用场景:

1. JDK 动态代理 (JDK Dynamic Proxy)

  • 原理:

    • JDK 动态代理是 Java 提供的内置代理机制,它通过反射在运行时动态地生成代理类。
    • 代理类会实现目标类实现的接口,并将接口中的方法调用委托给一个 InvocationHandler 对象来处理。
    • InvocationHandler 接口负责实现具体的增强逻辑,例如日志记录、安全检查、事务管理等。
  • 使用场景:

    • 目标类实现了接口: 如果目标类实现了接口,则 Spring AOP 默认使用 JDK 动态代理。
    • 简单 AOP 场景: 适用于简单的 AOP 场景,例如只需要对接口方法进行增强的情况。
    • 不需要代理类的构造函数: JDK 动态代理创建代理对象时,不需要调用目标类的构造函数。
  • 优点:

    • 简单易用: JDK 动态代理是 Java 内置的代理机制,使用起来比较简单。
    • 不需要第三方库: 不需要依赖第三方库。
    • 对接口友好: 代理类实现了目标类实现的接口,符合面向接口编程的思想。
  • 缺点:

    • 必须实现接口: 目标类必须实现接口,否则无法使用 JDK 动态代理。
    • 只能代理接口方法: 只能代理接口中定义的方法,无法代理类自身定义的方法。
  • 示例代码:

    java">// 接口
    interface MyInterface {
        void doSomething();
    }
    
    // 实现类
    class MyClass implements MyInterface {
        @Override
        public void doSomething() {
            System.out.println("MyClass is doing something...");
        }
    }
    
    // 调用处理器
    class MyInvocationHandler implements InvocationHandler {
        private Object target; // 被代理的对象
    
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before method: " + method.getName()); // 前置增强
            Object result = method.invoke(target, args); // 调用原始方法
            System.out.println("After method: " + method.getName()); // 后置增强
            return result;
        }
    }
    
    // 使用 JDK 动态代理
    public class JDKDynamicProxyExample {
        public static void main(String[] args) {
            MyInterface target = new MyClass(); // 创建目标对象
            MyInvocationHandler handler = new MyInvocationHandler(target); // 创建调用处理器
    
            // 创建代理对象
            MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                    MyInterface.class.getClassLoader(),
                    new Class[] {MyInterface.class},
                    handler
            );
    
            proxy.doSomething(); // 调用代理对象的方法,会被拦截
        }
    }
    

2. CGLIB 动态代理 (CGLIB Dynamic Proxy)

  • 原理:

    • CGLIB (Code Generation Library) 是一个强大的、高性能的代码生成库。
    • CGLIB 动态代理通过在运行时动态地生成目标类的子类来实现代理。
    • 代理类会继承目标类,并重写目标类的方法,在重写的方法中实现增强逻辑。
    • CGLIB 动态代理不需要目标类实现接口,可以直接代理类。
  • 使用场景:

    • 目标类没有实现接口: 如果目标类没有实现接口,则 Spring AOP 使用 CGLIB 动态代理。
    • 需要代理类自身定义的方法: 需要代理类自身定义的方法,而不仅仅是接口方法。
    • 需要更高的性能: 在某些情况下,CGLIB 动态代理的性能可能比 JDK 动态代理更好。
  • 优点:

    • 不需要实现接口: 目标类不需要实现接口,可以直接代理类。
    • 可以代理类自身定义的方法: 可以代理类自身定义的方法,而不仅仅是接口方法。
    • 性能较好: 在某些情况下,CGLIB 动态代理的性能可能比 JDK 动态代理更好。
  • 缺点:

    • 需要第三方库: 需要依赖 CGLIB 库。
    • 实现复杂: CGLIB 动态代理的实现比较复杂,需要生成目标类的子类。
    • final 方法无法代理: 无法代理被 final 修饰的方法。
    • 对构造函数有要求: CGLIB 创建代理对象时,需要调用目标类的构造函数。 如果目标类没有无参构造函数,则需要手动指定构造函数。
  • 示例代码:

    java">import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    // 目标类
    class MyClass {
        public void doSomething() {
            System.out.println("MyClass is doing something...");
        }
    }
    
    // 方法拦截器
    class MyMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("Before method: " + method.getName()); // 前置增强
            Object result = proxy.invokeSuper(obj, args); // 调用原始方法
            System.out.println("After method: " + method.getName()); // 后置增强
            return result;
        }
    }
    
    // 使用 CGLIB 动态代理
    public class CGLIBDynamicProxyExample {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer(); // 创建 Enhancer 对象
            enhancer.setSuperclass(MyClass.class); // 设置超类
            enhancer.setCallback(new MyMethodInterceptor()); // 设置回调
    
            MyClass proxy = (MyClass) enhancer.create(); // 创建代理对象
    
            proxy.doSomething(); // 调用代理对象的方法,会被拦截
        }
    }
    

3. Spring Boot 中的 AOP 配置

在 Spring Boot 项目中,可以通过以下方式配置 AOP:

  • 添加 AOP 依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  • 编写切面类:

    java">@Aspect
    @Component
    public class LoggingAspect {
    
        @Before("execution(* com.example.demo.service.*.*(..))")
        public void beforeAdvice(JoinPoint joinPoint) {
            System.out.println("Before executing method: " + joinPoint.getSignature());
        }
    }
    
  • 配置代理方式:

    默认情况下,Spring AOP 会根据目标类是否实现了接口来选择使用 JDK 动态代理或 CGLIB 动态代理。 可以通过 @EnableAspectJAutoProxy 注解的 proxyTargetClass 属性来强制使用 CGLIB 动态代理。

    java">@Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用 CGLIB 动态代理
    public class AopConfig {
        // ...
    }
    

总结:

特性JDK 动态代理CGLIB 动态代理
目标类要求必须实现接口不需要实现接口
代理对象生成方式实现接口继承类
性能一般较好
易用性简单复杂
是否需要第三方库是 (net.sf.cglib)
适用场景目标类实现了接口,简单 AOP 场景目标类没有实现接口,需要代理类自身定义的方法,性能要求较高
@EnableAspectJAutoProxy默认值:falseproxyTargetClass = true

在实际开发中,Spring AOP 会自动选择合适的代理方式。 如果没有特殊需求,可以使用默认配置。 如果需要强制使用 CGLIB 动态代理,可以设置 @EnableAspectJAutoProxy(proxyTargetClass = true)


http://www.niftyadmin.cn/n/5869204.html

相关文章

labview关于计时器的使用

通过使用计时器函数&#xff0c;可以对采集和保存实现很好的控制&#xff0c;因为之前通过等待函数有出现程序卡死的情况&#xff0c;这里用到定时器函数来实现时间控制。 根据用户输入的采集频率&#xff0c;和采集的单位来确定是否上次采集的时间间隔减去这次计时器的时间是…

故障诊断 | Matlab实现基于DBO-BP-Bagging多特征分类预测/故障诊断

故障诊断 | Matlab实现基于DBO-BP-Bagging多特征分类预测/故障诊断 目录 故障诊断 | Matlab实现基于DBO-BP-Bagging多特征分类预测/故障诊断分类效果基本介绍模型描述DBO-BP-Bagging蜣螂算法优化多特征分类预测一、引言1.1、研究背景和意义1.2、研究现状1.3、研究目的与方法 二…

补题A-E Codeforces Round 953 (Div. 2)

https://codeforces.com/contest/1979 A. Guess the Maximum 原题链接&#xff1a;https://codeforces.com/contest/1979/problem/A 求相邻元素的最大值的最小值。 #include <bits/stdc.h> using namespace std; #define IOS ios::sync_with_stdio(0), cin.tie(0), cout…

UE5网络通信架构解析

文章目录 前言一、客户端-服务器架构&#xff08;C/S Model&#xff09;二、对等网络架构&#xff08;P2P&#xff0c;非原生支持&#xff09;三、混合架构&#xff08;自定义扩展&#xff09;四、UE5网络核心机制 前言 UE5的网络通信主要基于客户端-服务器&#xff08;C/S&am…

《Keras 3 单眼深度估计》:此文为AI自动翻译

《Keras 3 单眼深度估计》 作者:Victor Basu 创建日期:2021/08/30 最后修改时间:2024/08/13 描述:使用卷积网络实现深度估计模型。 (i) 此示例使用 Keras 3 在 Colab 中查看 GitHub 源 介绍 深度估计是从 2D 图像推断场景几何结构的关键步骤。 单眼深度估计的目标是预…

【cuda学习日记】4.2 内存访问模式

4.2.1 缓存加载 如图&#xff0c;全局内存通过缓存来实现加载/存储。所有对全局内存的访问都会通过二级缓存&#xff0c;也有许多访问会通过一级缓存。如果这两级缓存都被用到&#xff0c;那么内存访问是由一个128字节的内存事务实现的。如果只使用了二级缓存&#xff0c;那么这…

九九乘法表 matlab

J的第一行的1分别乘以I的九列数&#xff0c;就是1的乘法表 1*11 1*22 。。。

滑动验证组件-微信小程序

微信小程序-滑动验证组件&#xff0c;直接引用就可以了&#xff0c;效果如下&#xff1a; 组件参数&#xff1a; 1.enable-close&#xff1a;是否允许关闭&#xff0c;默认true 2.bind:onsuccess&#xff1a;验证后回调方法 引用方式&#xff1a; <verification wx:if&qu…