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

相关推荐
乐悠小码2 分钟前
Java设计模式精讲---04原型模式
java·设计模式·原型模式
秋风&萧瑟6 分钟前
【C++】智能指针介绍
java·c++·算法
QiZhang | UESTC8 分钟前
JAVA算法练习题day67
java·python·学习·算法·leetcode
毕设源码-朱学姐19 分钟前
【开题答辩全过程】以 基于java的民宿管理小程序为例,包含答辩的问题和答案
java·开发语言·小程序
それども24 分钟前
List 添加元素提示 UnsupportedOperationException
java
ᐇ95928 分钟前
Java集合框架:深入理解List与Set及其实现类
java·开发语言
无名-CODING29 分钟前
Java集合List详解:从入门到精通
java·windows·list
laplace012338 分钟前
JAVA-Redis上
java·redis·spring
不要喷香水42 分钟前
26.java openCV4.x 入门-Imgproc之图像尺寸调整与区域提取
java·人工智能·opencv·计算机视觉
脸大是真的好~1 小时前
黑马JAVAWeb - SpringAOP
java