问题

动态代理有哪些实现方式?各自的优缺点是什么?

答案

一、核心概念

动态代理是在运行时动态生成代理类的技术,无需手动编写代理类代码。Java 中主要有以下几种实现方式:

  1. JDK 动态代理(基于接口)
  2. CGLIB 动态代理(基于继承)
  3. Javassist(字节码操作)
  4. ByteBuddy(现代字节码生成库)

二、JDK 动态代理

实现原理

基于 Java 反射机制,通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口实现。代理类在运行时动态生成,必须实现一个或多个接口。

代码示例

// 接口定义
interface UserService {
    void addUser(String name);
    String getUser(int id);
}

// 目标类
class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户: " + name);
    }

    @Override
    public String getUser(int id) {
        return "用户" + id;
    }
}

// InvocationHandler 实现
class LogHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法调用前: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("方法调用后: " + method.getName());
        return result;
    }
}

// 使用
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LogHandler(target)
);
proxy.addUser("张三");

优缺点

优点��

  • JDK 原生支持,无需额外依赖
  • 性能较好(JDK 8+ 优化后)
  • 代码简洁,易于理解

缺点

  • 只能代理实现了接口的类
  • 无法代理 final 方法
  • 反射调用有一定性能开销

三、CGLIB 动态代理

实现原理

基于 ASM 字节码框架,通过继承目标类生成子类,重写父类方法实现代理。不要求目标类实现接口。

代码示例

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

// 目标类(无需实现接口)
class OrderService {
    public void createOrder(String orderId) {
        System.out.println("创建订单: " + orderId);
    }
}

// MethodInterceptor 实现
class LogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB 前置增强: " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("CGLIB 后置增强: " + method.getName());
        return result;
    }
}

// 使用
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new LogInterceptor());
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder("ORDER-001");

优缺点

优点

  • 可以代理没有实现接口的类
  • 性能优于 JDK 动态代理(方法调用不走反射)
  • Spring AOP 默认使用 CGLIB

缺点

  • 需要额外依赖(cglib.jar)
  • 无法代理 final 类和 final 方法
  • 生成的子类会增加内存开销
  • 构造函数会被调用两次

四、Javassist

实现原理

直接操作字节码,提供高层 API 简化字节码编程。可以在运行时修改类或生成新类。

代码示例

import javassist.*;

ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("com.example.DynamicService");

// 添加方法
CtMethod method = CtNewMethod.make(
    "public void sayHello() { System.out.println(\"Hello from Javassist\"); }",
    ctClass
);
ctClass.addMethod(method);

// 生成类
Class<?> clazz = ctClass.toClass();
Object instance = clazz.newInstance();
clazz.getMethod("sayHello").invoke(instance);

优缺点

优点

  • 功能强大,可以直接修改字节码
  • API 相对友好,支持源码级别的字节码操作
  • 性能优秀

缺点

  • 学习曲线较陡
  • 调试困难
  • 不当使用可能导致类加载问题

五、ByteBuddy

实现原理

现代化的字节码生成库,提供流式 API,比 Javassist 更易用。被 Mockito、Hibernate 等框架采用。

代码示例

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;

Class<?> dynamicType = new ByteBuddy()
    .subclass(Object.class)
    .method(ElementMatchers.named("toString"))
    .intercept(FixedValue.value("Hello ByteBuddy"))
    .make()
    .load(getClass().getClassLoader())
    .getLoaded();

System.out.println(dynamicType.newInstance().toString());
// 输出: Hello ByteBuddy

优缺点

优点

  • API 设计优雅,易于使用
  • 性能优秀,生成代码质量高
  • 支持 Java 8+ 新特性
  • 类型安全

缺点

  • 相对较新,社区资源较少
  • 包体积较大

六、性能对比

实现方式 创建速度 调用速度 内存占用
JDK 动态代理 中等(反射)
CGLIB 慢(生成子类) 快(直接调用) 较大
Javassist 中等 中等
ByteBuddy 中等 中等

七、实际应用场景

JDK 动态代理

  • Spring AOP(目标类实现接口时)
  • MyBatis Mapper 接口代理
  • RPC 框架的客户端代理

CGLIB

  • Spring AOP(目标类未实现接口时)
  • Hibernate 懒加载
  • Mock 框架(Mockito)

Javassist/ByteBuddy

  • 字节码增强框架(SkyWalking、Arthas)
  • ORM 框架的实体增强
  • 热部署工具

八、面试答题要点

  1. JDK vs CGLIB:JDK 基于接口,CGLIB 基于继承;JDK 用反射,CGLIB 用字节码
  2. 选择依据:有接口用 JDK,无接口用 CGLIB;Spring 默认策略是有接口用 JDK,否则用 CGLIB
  3. 性能考量:CGLIB 创建慢但调用快,JDK 相反;实际应用中差异不大
  4. 限制条件:JDK 需要接口,CGLIB 不能代理 final 类/方法
  5. 实际应用:能说出 Spring AOP、MyBatis、RPC 框架等具体使用场景