Spring实现AOP功能的原理:代理模式(JDK动态代理与CGLIB字节码生成技术代理)的理解

JDK 动态代理和 CGLIB 代理是 Java 中两种常见的动态代理技术。它们的核心作用是 在运行时生成代理对象,用于增强原始对象的功能(如 AOP 切面编程、拦截方法调用等)。


① JDK 动态代理

JDK 动态代理基于 java.lang.reflect.Proxy 和 InvocationHandler 接口。它 仅能代理实现了接口的类 ,通过 Proxy.newProxyInstance 方法在运行时生成代理对象。

工作原理

  1. 代理类实现目标接口,并在 invoke 方法中拦截所有方法调用。
  2. 代理对象是 Proxy 类的子类,JVM 在运行时动态生成该类。
  3. 调用方法时,代理对象会调用 InvocationHandler 的 invoke 方法。

示例

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

// 1. 定义接口
interface UserService {
    void save(String name);
}

// 2. 目标对象
class UserServiceImpl implements UserService {
    @Override
    public void save(String name) {
        System.out.println("保存用户: " + name);
    }
}

// 3. 创建 InvocationHandler 处理器
class MyInvocationHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日志: 方法 " + method.getName() + " 被调用,参数:" + args[0]);
        Object result = method.invoke(target, args);
        System.out.println("日志: 方法 " + method.getName() + " 调用完成");
        return result;
    }
}

// 4. 使用动态代理创建代理对象
public class JDKProxyDemo {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(),
                new MyInvocationHandler(target)
        );

        proxy.save("张三");
    }
}

输出

复制代码
日志: 方法 save 被调用,参数:张三
保存用户: 张三
日志: 方法 save 调用完成

优缺点

✅ 优点:

  • 依赖 JDK,直接使用,无需额外库。
  • 线程安全,稳定性较高。

❌ 缺点:

  • 只能代理实现了接口的类,不能代理普通类。
  • 由于反射调用 invoke,性能稍差(比 CGLIB 慢)。

② CGLIB 动态代理

CGLIB(Code Generation Library)是一个强大的 字节码生成技术 ,可以代理 没有实现接口的普通类,其底层依赖 ASM(Java 字节码操作库)。

工作原理

  1. CGLIB 生成 目标类的子类,重写其方法,并在方法调用时增强功能。
  2. 由于 CGLIB 直接操作字节码,比 JDK 代理更快(因为少了反射调用)。
  3. 但由于是继承方式,不能代理 final 方法。

示例

需要 引入 CGLIB 依赖:

XML 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 1. 目标类(没有实现接口)
class UserService {
    public void save(String name) {
        System.out.println("保存用户: " + name);
    }
}

// 2. CGLIB 代理类
class CglibProxy implements MethodInterceptor {
    private final Object target;

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

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("日志: 方法 " + method.getName() + " 被调用,参数:" + args[0]);
        Object result = proxy.invoke(target, args);
        System.out.println("日志: 方法 " + method.getName() + " 调用完成");
        return result;
    }

    // 生成代理对象
    public Object createProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
}

// 3. 使用 CGLIB 创建代理对象
public class CGLIBProxyDemo {
    public static void main(String[] args) {
        UserService target = new UserService();
        UserService proxy = (UserService) new CglibProxy(target).createProxy();
        
        proxy.save("李四");
    }
}

输出

复制代码
日志: 方法 save 被调用,参数:李四
保存用户: 李四
日志: 方法 save 调用完成

优缺点

✅ 优点:

  • 可以代理普通类(不要求接口)。
  • 性能比 JDK 代理更好(不使用反射,而是直接生成字节码)。

❌ 缺点:

  • 不能代理 final 类或 final 方法(因为 CGLIB 通过继承来生成代理)。
  • 额外依赖 CGLIB,需要额外的 Jar 包(不过 Spring 已内置 CGLIB)。
  • 比 JDK 代理稍微复杂,但更灵活。

JDK 代理 vs CGLIB 代理 对比

特性 JDK 动态代理 CGLIB 代理
代理方式 反射 InvocationHandler 继承+ASM 字节码生成
是否需要接口 需要(只能代理接口) 不需要(可代理普通类)
代理对象类型 Proxy 生成的匿名类 目标类的子类
是否可代理 final 方法 可以 ❌ 不可以
性能 较慢(基于反射) 较快(直接修改字节码)
依赖 JDK 自带 需额外依赖 CGLIB
典型应用 java.lang.reflect.Proxy、Spring AOP(基于接口) Spring AOP(无接口时)

使用场景总结

  • JDK 动态代理 适用于 已有接口 的情况,比如 Service 层的 UserService、OrderService。
  • CGLIB 动态代理 适用于 无接口 的普通类,比如 UserService 直接写成普通类。

Spring AOP 使用情况

  • 如果被代理的类实现了接口 ,Spring 默认使用 JDK 动态代理。
  • 如果没有接口 ,Spring 使用 CGLIB 代理 (@EnableAspectJAutoProxy(proxyTargetClass = true) 强制 CGLIB)。

总结

  • JDK 动态代理 是基于接口的反射机制,适用于 接口代理。
  • CGLIB 代理 通过 字节码增强 来生成子类,适用于 普通类代理。
  • Spring AOP 结合两者,默认用 JDK,必要时才用 CGLIB。

对于日常开发,如果类有接口,优先使用 JDK 动态代理 ;否则,使用 CGLIB。

相关推荐
鱼宵7 小时前
Stream 终端操作一次讲透:collect、count、reduce 怎么选
java·stream·终端操作
白山编程大哥7 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
Linux运维技术栈7 小时前
CVE-2026-91403 高危漏洞修复:Rocky Linux 9 多版本 JDK 环境清理实操
java·linux·运维
On The Road :7 小时前
Java 多线程详解:从入门到线程安全
java·python·安全
Shaoshing8 小时前
LeetCode100 49. 字母异位词分组
java·算法
划破黑暗的第一缕曙光9 小时前
[C++]:5.STL:string(2)
java·c++·redis
hhhjhl9 小时前
Undici 跨端三方库仓颉适配全流程
java·开发语言·ubuntu
wangchunyu1149 小时前
JAVA JVM内存结构与运行时数据区
java·开发语言·jvm
光锥智能9 小时前
海康机器人举行2026移动机器人新品发布会:箱式存拣升级,「明智」系列发布
java·前端·机器人
行思理10 小时前
Nginx 跨站请求(CORS)配置完全指南:从原理到生产实践
java·前端·nginx