深入浅出分析JDK动态代理与CGLIB动态代理的区别

深入浅出分析JDK动态代理与CGLIB动态代理的区别

动态代理是Java开发中非常重要的技术,尤其在AOP(面向切面编程)中应用广泛,比如Spring框架。JDK动态代理和CGLIB动态代理是两种常见的实现方式,它们各有特点。今天我们就来循序渐进地拆解它们的区别,顺便解决你"记不住API"的困扰------别担心,我们不死扣API,而是抓重点!

一、什么是动态代理?

先从概念说起。动态代理就是在程序运行时,通过某种机制动态生成一个代理对象,这个对象可以替目标对象执行方法,同时还能"加点料"(比如日志、事务控制)。想象一下,代理对象就像个中介,帮你干活还能顺便加点自己的小动作。

Java中有两种主流动态代理:

  1. JDK动态代理:Java自带,基于接口实现。
  2. CGLIB动态代理:第三方库,基于继承实现。

接下来,我们一步步拆解它们的区别。


二、JDK动态代理:基于接口的"正统"方式

工作原理

JDK动态代理的核心是java.lang.reflect.Proxy类。它通过运行时生成一个实现了目标接口的代理类,来完成代理功能。具体步骤是:

  1. 你得有个接口(比如UserService)。
  2. 目标类(比如UserServiceImpl)实现这个接口。
  3. Proxy.newProxyInstance生成代理对象,指定接口和一个InvocationHandler(处理器)。
  4. 调用代理对象的方法时,实际会交给InvocationHandlerinvoke方法执行。

简单例子

假设我们有个接口和实现类:

java 复制代码
public interface UserService {
    void sayHello();
}

public class UserServiceImpl implements UserService {
    public void sayHello() {
        System.out.println("Hello!");
    }
}

用JDK动态代理生成代理:

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

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 InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Before...");
                    Object result = method.invoke(target, args); // 通过反射调用目标方法
                    System.out.println("After...");
                    return result;
                }
            }
        );
        proxy.sayHello();
    }
}

输出:

erlang 复制代码
Before...
Hello!
After...
InvocationHandler的invoke参数
  • proxy:代理对象本身。
  • method:被调用的目标方法。
  • args:方法的参数数组。

注意,这里需要手动传入target(目标对象),通过method.invoke(target, args)反射调用。

特点

  1. 基于接口:必须有接口,没接口它就无能为力。
  2. 用反射invoke方法通过反射调用目标方法,性能上稍微慢一点。
  3. 官方支持:JDK自带,无需额外依赖。

三、CGLIB动态代理:基于继承的"灵活"方式

工作原理

CGLIB(Code Generation Library)是第三方库,它通过字节码操作(ASM框架)在运行时生成目标类的子类作为代理类。代理类会重写父类的方法,并在其中插入额外的逻辑。

简单例子

假设我们有个类(注意,不需要接口):

java 复制代码
public class UserServiceImpl {
    public void sayHello() {
        System.out.println("Hello!");
    }
}

用CGLIB生成代理:

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("Before...");
                Object result = proxy.invokeSuper(obj, args); // 调用父类方法
                System.out.println("After...");
                return result;
            }
        });
        UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
        proxy.sayHello();
    }
}

输出:

erlang 复制代码
Before...
Hello!
After...
MethodInterceptor的intercept参数
  • obj:代理对象本身(CGLIB生成的子类实例)。
  • method:被拦截的目标方法(java.lang.reflect.Method类型)。
  • args:方法的参数数组,和JDK的args一样。
  • methodProxy:CGLIB特有的MethodProxy对象,封装了对父类方法的快速调用方式,通过invokeSuper直接调用,避免反射。

重点methodProxy是CGLIB效率高的关键。它不像JDK那样依赖反射,而是通过字节码直接调用父类方法。

特点

  1. 基于继承 :通过生成子类实现代理,所以目标类不能是final
  2. 无需反射:直接调用父类方法,效率更高。
  3. 无需接口:不像JDK动态代理强制要求接口,CGLIB更灵活。

四、核心区别对比

维度 JDK动态代理 CGLIB动态代理
实现方式 基于接口(通过Proxy类) 基于继承(生成子类)
性能 用反射,稍慢 字节码操作,效率更高
依赖 JDK自带,无需额外库 需要CGLIB库(Spring已集成)
适用场景 必须有接口 无接口也能用,但不能是final类
回调参数 proxy, method, args obj, method, args, methodProxy
调用方式 method.invoke(target, args) proxy.invokeSuper(obj, args)

五、Spring Boot为什么默认用CGLIB?

Spring Boot默认使用CGLIB,原因有以下几点:

  1. 灵活性:Spring的AOP常用于无接口的类(比如普通的POJO),CGLIB无需接口支持更通用。
  2. 性能:CGLIB避免反射,执行效率更高,尤其在高并发场景下。
  3. 集成方便 :Spring早就把CGLIB集成进来了(spring-core依赖里包含),无需额外配置。

不过,Spring也支持JDK动态代理。如果目标类实现了接口,可以通过配置(proxyTargetClass=false)切换到JDK动态代理。


六、怎么记住这些?

你说记不住API,其实没必要死记硬背。我们抓住几个关键点:

  1. JDK动态代理 :想到"接口"和"反射",API主要是Proxy.newProxyInstanceInvocationHandler
  2. CGLIB :想到"继承"和"效率高",API主要是EnhancerMethodInterceptor,记住methodProxy是性能关键。
  3. Spring Boot:默认CGLIB,记"灵活+性能"就够了。

下次回忆时,问自己:

  • 有没有接口?有→JDK动态代理,没→CGLIB。
  • 性能敏感吗?敏感→CGLIB。
  • Spring默认啥?CGLIB!

七、总结

JDK动态代理和CGLIB动态代理各有千秋:

  • JDK动态代理是"正统选手",适合有接口的场景,但反射让它稍慢。
  • CGLIB是"灵活高手",通过继承和字节码操作(尤其是methodProxy)实现高效代理,Spring Boot也因此偏爱它。

希望这篇博客能帮你理清思路,尤其是CGLIB的intercept参数和它与JDK的区别。下次再遇到动态代理时,不用死记API,只需抓住"接口还是继承""反射还是字节码",就能轻松搞定!

相关推荐
Vitalia23 分钟前
从零开始学Rust:枚举(enum)与模式匹配核心机制
开发语言·后端·rust
飞飞翼1 小时前
python-flask
后端·python·flask
草捏子2 小时前
最终一致性避坑指南:小白也能看懂的分布式系统生存法则
后端
一个public的class2 小时前
什么是 Java 泛型
java·开发语言·后端
头孢头孢4 小时前
k8s常用总结
运维·后端·k8s
TheITSea4 小时前
后端开发 SpringBoot 工程模板
spring boot·后端
Asthenia04124 小时前
编译原理中的词法分析器:从文本到符号的桥梁
后端
Asthenia04124 小时前
用RocketMQ和MyBatis实现下单-减库存-扣钱的事务一致性
后端
Pasregret4 小时前
04-深入解析 Spring 事务管理原理及源码
java·数据库·后端·spring·oracle
Micro麦可乐4 小时前
最新Spring Security实战教程(七)方法级安全控制@PreAuthorize注解的灵活运用
java·spring boot·后端·spring·intellij-idea·spring security