JDK 动态代理 vs CGLIB:原理、区别与 Spring AOP 底层揭秘

在 Spring 框架中,我们常常通过 @Transactional@Cacheable 或自定义切面来为方法添加日志 📝、事务、缓存等横切逻辑。这些功能无需修改原有代码,却能"自动"生效------这背后的核心技术,正是 动态代理(Dynamic Proxy) ⚙️。

Java 中最常用的两种动态代理实现是 JDK 动态代理 🔌 和 CGLIB 动态代理 🛠️。Spring AOP 正是基于它们,在运行时为目标对象创建代理,从而实现方法的增强。两者的实现机制截然不同:JDK 代理依赖接口与反射 ,CGLIB 则通过继承生成子类 。

你是否曾遇到过事务失效 、代理不生效的问题?这往往与代理方式的选择有关 🤔。理解 JDK 代理与 CGLIB 的原理与差异,不仅能帮你深入掌握 Spring AOP 🔍,还能在实际开发中做出更合理的架构决策。

本文将带你从代码 💻 到原理 📚,全面解析这两种代理机制,并梳理它们在 Spring 中的自动选择策略 🧭。

🎯 JDK 动态代理

JDK 动态代理是 Java 原生支持的代理机制,要求目标类必须实现接口。代理对象在运行时通过 Proxy 类创建,并需要一个 InvocationHandler 来处理方法调用。

✨ 关键特点:

  • 基于接口:JDK 动态代理只支持接口的代理,代理对象必须实现目标类的接口。
  • 运行时代理:代理对象是运行时通过反射机制动态生成的,而不是编译时生成。
  • 灵活性和可扩展性:JDK 动态代理非常适用于接口的代理,可以在运行时选择代理对象的行为。

🧩 JDK 动态代理原理

JDK 动态代理的核心是 InvocationHandler 接口,通过该接口可以拦截所有方法调用,并在目标方法执行前后执行增强逻辑。Proxy.newProxyInstance() 方法用来生成代理类,代理类的所有方法都会被 invoke() 方法拦截。

java 复制代码
import java.lang.reflect.*;

interface HelloService {
    void sayHello(String name);
}

class HelloServiceImpl implements HelloService {
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

class HelloServiceProxy implements InvocationHandler {
    private Object target;

    public HelloServiceProxy(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;
    }
}

public class ProxyExample {
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
                helloService.getClass().getClassLoader(),
                helloService.getClass().getInterfaces(),
                new HelloServiceProxy(helloService));

        proxy.sayHello("World");
    }
}

分析:

  • HelloService 是接口,HelloServiceImpl 是实现类,HelloServiceProxy 是代理类。
  • HelloServiceProxy 实现了 InvocationHandler 接口,重写了 invoke() 方法,在目标方法执行前后进行增强。

🔍 JDK 动态代理的限制:

  • 只能代理接口:只能用于代理实现了接口的类。如果目标类没有实现接口,JDK 动态代理无法使用。
  • 性能相对较差:由于代理对象通过反射机制生成,并且每次方法调用都会触发反射,性能会略微受到影响。

🎯 CGLIB 动态代理

CGLIB (Code Generation Library) 是一个第三方库,它通过 继承 目标类并重写其方法来生成动态代理。与 JDK 动态代理不同,CGLIB 代理不需要目标类实现接口,因此能够代理普通类。

✨ 关键特点:

  • 基于继承:CGLIB 动态代理通过生成目标类的子类来创建代理对象。目标类无需实现接口。
  • 字节码生成:CGLIB 使用字节码技术生成代理类,能够高效地实现代理。
  • 代理生成方式:CGLIB 会通过继承目标类并重写目标类的方法来实现增强。

🧩 CGLIB 动态代理原理

CGLIB 动态代理的原理是通过 字节码操作,生成目标类的代理子类,并重写目标类的方法来进行增强。这意味着生成的代理类是目标类的一个子类,并且对方法调用进行拦截。

java 复制代码
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

class HelloService {
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

class HelloServiceProxy implements MethodInterceptor {
    private Object target;

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

    @Override
    public Object intercept(Object obj, java.lang.reflect.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;
    }
}

public class CglibProxyExample {
    public static void main(String[] args) {
        HelloService helloService = new HelloService();
        HelloServiceProxy helloServiceProxy = new HelloServiceProxy(helloService);

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloService.class);
        enhancer.setCallback(helloServiceProxy);

        HelloService proxy = (HelloService) enhancer.create();
        proxy.sayHello("World");
    }
}

分析:

  • HelloService 是目标类,HelloServiceProxy 是代理类,Enhancer 用来生成代理类。
  • CGLIB 动态代理通过继承目标类生成代理子类,并重写目标类的方法进行增强。
  • CGLIB 动态代理 使用 invokeSuper 是因为代理是目标的子类invokeSuper 是 CGLIB 提供的高效方式来调用父类方法,避免反射开销,并保持代理链的完整性。

🔍 CGLIB 的限制:

  • 不能代理 final 类和方法 :由于 CGLIB 是基于继承的方式实现代理,因此它无法代理 final 类和 final 方法。

  • 内存消耗较大:CGLIB 需要生成新的字节码类,因此在内存和性能上较为消耗,特别是在大量代理对象的情况下。

🔬 JDK 动态代理 vs CGLIB 动态代理

特性 JDK 动态代理 CGLIB 动态代理
代理方式 基于接口代理 基于类的继承代理
目标要求 目标类必须实现接口 目标类无需实现接口
代理类生成方式 使用反射生成代理类 通过字节码生成目标类的子类
性能 在早期版本中性能较差,现代 JVM(尤其是开启 JIT 后)性能已大幅提升 性能较好,因为直接生成子类
线程安全 和 CGLIB 一样,默认非线程安全 和 JDK 动态代理一样,默认非线程安全
限制 只能代理实现了接口的类的public方法 无法代理 final 类和方法

📊 适用场景:

  • JDK 动态代理 :适用于目标类实现了接口,并且我们希望基于接口进行代理的场景。常见的应用场景是 Spring AOP,它通常使用 JDK 动态代理来增强服务层接口的方法。
  • CGLIB 动态代理 :适用于目标类没有实现接口,或者需要对类的内部方法进行代理的场景。例如,Spring 的事务管理(@Transactional)通常使用 CGLIB 代理,如果目标类没有实现接口。

🔬 Spring AOP 中的动态代理

在 Spring AOP 中,动态代理用于增强业务逻辑。当你使用注解(例如 @Transactional)时,Spring AOP 在底层自动选择使用 JDK动态代理CGLIB动态代理

  • 如果目标对象实现了接口,默认使用JDK动态代理
  • 如果目标对象没有实现接口,使用CGLIB动态代理
  • 可通过配置强制使用 CGLIB(如 @EnableAspectJAutoProxy(proxyTargetClass = true))。

特点:

  • 开发者无需手动编写代理逻辑,只需通过注解(如 @Aspect, @Before, @After)定义切面。
  • 更高级的 AOP(如 AspectJ)可以实现编译期或类加载期织入,功能更强。

示例(Spring AOP):

java 复制代码
@Aspect
@Component
public class LogAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint jp) {
        System.out.println("方法执行前:" + jp.getSignature());
    }
}

📅 总结

  • JDK 动态代理适用于有接口的类,性能较差但灵活。
  • CGLIB 动态代理 适用于没有接口的类,性能较好,但无法代理 final 类和方法。
  • 在实际项目中,根据目标对象的实现方式(是否有接口),可以选择合适的代理方式。特别是在 Spring 框架中,选择 JDK 动态代理或 CGLIB 动态代理取决于目标类是否实现了接口。
相关推荐
00后程序员张4 小时前
Swoole HTTPS 实战,在生产环境部署、性能权衡与排查流程
后端·ios·小程序·https·uni-app·iphone·swoole
代码充电宝4 小时前
LeetCode 算法题【中等】189. 轮转数组
java·算法·leetcode·职场和发展·数组
我命由我123454 小时前
PDFBox - PDDocument 与 byte 数组、PDF 加密
java·服务器·前端·后端·学习·java-ee·pdf
花哥码天下4 小时前
Oracle下载JDK无需登录
java·开发语言
考虑考虑5 小时前
go格式化时间
后端·go
摇滚侠5 小时前
Spring Boot 3零基础教程,yml语法细节,笔记16
java·spring boot·笔记
wei8440678725 小时前
本地项目第一次推送到gitee上的完整命令
java·android studio
星球奋斗者5 小时前
计算机方向如何才能更好的找到工作?(成长心得)
java·后端·考研·软件工程·改行学it
Jabes.yang5 小时前
互联网大厂Java面试:缓存技术与监控运维的深度探讨
java·面试指南·缓存技术·监控运维