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

1,265 阅读5分钟

深入浅出分析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方法执行。

简单例子

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

public interface UserService {
    void sayHello();
}

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

用JDK动态代理生成代理:

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();
    }
}

输出:

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

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

特点

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

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

工作原理

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

简单例子

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

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

用CGLIB生成代理:

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();
    }
}

输出:

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, argsobj, 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,只需抓住“接口还是继承”“反射还是字节码”,就能轻松搞定!