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

相关推荐
翻滚丷大头鱼6 小时前
Java 集合Collection—List
java·开发语言
sanggou6 小时前
License 集成 Spring Gateway:解决 WebFlux 非阻塞与 Spring MVC Servlet 阻塞兼容问题
spring·gateway·mvc
敲键盘的肥嘟嘟左卫门7 小时前
StringBuilder类的数据结构和扩容方式解读
java
索迪迈科技7 小时前
java后端工程师进修ing(研一版 || day40)
java·开发语言·学习·算法
十碗饭吃不饱7 小时前
net::ERR_EMPTY_RESPONSE
java·javascript·chrome·html5
白初&7 小时前
SpringBoot后端基础案例
java·spring boot·后端
哈基米喜欢哈哈哈7 小时前
ThreadLocal 内存泄露风险解析
java·jvm·面试
萌新小码农‍7 小时前
Java分页 Element—UI
java·开发语言·ui
鼠鼠我捏,要死了捏8 小时前
深入实践G1垃圾收集器调优:Java应用性能优化实战指南
java·g1·gc调优
书院门前细致的苹果8 小时前
ArrayList、LinkedList、Vector 的区别与底层实现
java