【Java 开发日记】我们来说一说动态代理

【Java 开发日记】我们来说一说动态代理

动态代理是 Java 中非常核心且高频出现的技术之一,几乎所有现代框架(Spring、MyBatis、Dubbo、Shiro、Hibernate 等)的很多“魔法”功能都依赖它。

今天我们把动态代理从原理到实战、从底层到常见误区完整梳理一遍。

1. 什么是动态代理?它和静态代理的区别

静态代理
编译期就写好代理类,代理类和目标类实现同一个接口,代理类内部持有一个目标对象引用。

// 接口
interface UserService {
    void save();
}

// 目标类
class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("保存用户");
    }
}

// 静态代理类(提前写好)
class UserServiceProxy implements UserService {
    private UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    public void save() {
        System.out.println("【静态代理】开启事务");
        target.save();
        System.out.println("【静态代理】提交事务");
    }
}

缺点

  • 每个接口都要写一个代理类
  • 如果接口方法增加,代理类也要改
  • 代码重复严重

动态代理
运行时生成代理类,不需要提前写代理类代码。
Java 中主要有两种实现方式:

  1. JDK 动态代理(基于接口)
  2. CGLIB 动态代理(基于继承)

2. JDK 动态代理(最经典、最基础)

核心类:java.lang.reflect.Proxy + InvocationHandler

三个关键要素

  • 接口(被代理对象必须实现接口)
  • InvocationHandler(真正决定“代理要做什么”的地方)
  • Proxy.newProxyInstance()(运行时生成代理对象)

完整代码示例

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 接口
interface UserService {
    void saveUser(String name);
    String getUserName(int id);
}

// 目标实现
class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String name) {
        System.out.println("保存用户:" + name);
    }

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

// 自定义 InvocationHandler(核心:在这里写增强逻辑)
class LoggingHandler implements InvocationHandler {

    // 真正的目标对象
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强
        System.out.println("【JDK动态代理】方法开始:" + method.getName());
        long start = System.currentTimeMillis();

        // 执行目标方法
        Object result = method.invoke(target, args);

        // 后置增强
        long end = System.currentTimeMillis();
        System.out.println("【JDK动态代理】方法结束:" + method.getName() + ",耗时:" + (end - start) + "ms");

        return result;
    }
}

// 测试
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 LoggingHandler(target)                    // 调用处理器
        );

        proxy.saveUser("张三");
        System.out.println(proxy.getUserName(100));
    }
}

输出示例

【JDK动态代理】方法开始:saveUser
保存用户:张三
【JDK动态代理】方法结束:saveUser,耗时:0ms
【JDK动态代理】方法开始:getUserName
用户100
【JDK动态代理】方法结束:getUserName,耗时:0ms

3. CGLIB 动态代理(无接口也能代理)

核心思想:通过字节码生成技术,运行时生成目标类的子类,然后重写方法,在子类方法中插入增强逻辑。

优点

  • 不需要目标类实现接口
  • 可以代理普通类(只要不是 final 类)

缺点

  • final 类、final 方法无法代理
  • 性能首次生成稍慢(但后续缓存后很快)

现代 Spring Boot 默认使用 CGLIB(即使有接口也优先 CGLIB)

简单代码示例(使用 cglib 库):

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

class OrderService {
    public void createOrder(String orderNo) {
        System.out.println("创建订单:" + orderNo);
    }
}

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

public class CglibDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class);
        enhancer.setCallback(new LogInterceptor());

        OrderService proxy = (OrderService) enhancer.create();
        proxy.createOrder("ORD-20250211");
    }
}

4. Spring AOP 中的动态代理选择(2025–2026 现状)

条件Spring AOP 默认使用的代理方式说明
目标类实现了接口JDK 动态代理(历史默认)早期默认
目标类没有接口CGLIB必须
Spring Boot 2.x+ 默认配置CGLIBspring.aop.proxy-target-class=true
强制使用 JDK 代理设置 proxy-target-class=false必须有接口

现代推荐:保持默认 CGLIB,行为更一致,兼容性更好。

5. 动态代理最常见的面试/实战问题

  1. JDK 动态代理和 CGLIB 的区别?
    接口 vs 继承、能否代理无接口类、final 方法、性能等
  2. 为什么 Spring Boot 默认改成 CGLIB?
    避免接口代理导致的类型转换问题、@Configuration 类必须子类化
  3. this 调用方法为什么不走代理?(自调用失效)
    因为 this 是原始对象,不是代理对象 解决办法:
  • 注入自己(@Autowired UserService self
  • 使用 AopContext.currentProxy()(需开启 expose-proxy)
  1. 动态代理生成的类长什么样?
    JDK:$Proxy0$Proxy1
    CGLIB:UserService$$EnhancerBySpringCGLIB$$xxxx
  2. 动态代理能代理 static、private、final 方法吗?
    都不能(static 是类方法,private/final 不能被重写/覆盖)

6. 一句话总结动态代理

动态代理就是在运行时生成一个代理对象,这个代理对象要么实现目标接口(JDK),要么继承目标类(CGLIB),拦截方法调用,在调用前后插入我们自定义的增强逻辑。

希望这篇日记把动态代理的来龙去脉讲清楚了。

如果你想继续聊下面任一方向,随时告诉我:

  • Spring AOP 动态代理完整源码流程
  • InvocationHandler 的 invoke 方法底层原理
  • 手写一个简单的 Spring 风格 AOP
  • 动态代理在 MyBatis、Dubbo 中的应用
  • 性能对比(JDK vs CGLIB vs ByteBuddy vs Javassist)

继续写日记~

文章已创建 4631

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部