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

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


JDK 动态代理

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

工作原理

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

示例

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 层的 UserServiceOrderService
  • CGLIB 动态代理 适用于 无接口 的普通类,比如 UserService 直接写成普通类。

Spring AOP 使用情况

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

总结

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

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

相关推荐
阿维的博客日记5 小时前
Hippo4j 线程池监控平台部署手册
java·spring boot·后端
C+++Python7 小时前
详细介绍一下Java泛型的通配符
java·windows·python
JosieBook8 小时前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
一生了无挂9 小时前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
李白的天不白9 小时前
使用 SmartAdmin 进行前后端开发
java·前端
swordbob9 小时前
Spring 单例 Bean 是线程安全的吗?
java·开发语言
2601_9516437710 小时前
Python第一,Java跌出前三,C语言杀回来了
java·c语言·python·编程语言排行·技术趋势
腾科IT教育11 小时前
Spring AI Alibaba 向量(VectorStore)
人工智能·spring·microsoft
IT 行者12 小时前
GitHub Spec Kit 实战(五):/speckit.tasks 怎么拆——Spec Kit 五部曲收官
java·ai编程·claude
(Charon)12 小时前
【C++ 面试高频基础:指针、引用、const、static、new/delete 总结】
java·开发语言