本文最后更新于 2025-11-20,文章内容可能已经过时。

Java 动态代理是 Java 反射机制中的一项强大功能,它允许我们在运行时动态创建代理对象,从而在不修改原始代码的前提下,对目标对象的方法调用进行拦截、增强或替换。它是实现 AOP(面向切面编程)的核心技术之一,也是 Spring、MyBatis 等主流框架的基础。


一、什么是动态代理?

动态代理 = 接口 + 目标对象 + 代理处理器 + 运行时生成的代理类

与静态代理(需要手动编写代理类)不同,动态代理是在程序运行时自动生成代理类,无需提前编写 .java 文件。这种“无侵入式”的增强方式,让代码更灵活、可维护性更强。

核心特点:

  • 基于接口(JDK 动态代理)
  • 运行时生成代理类
  • 方法调用可被拦截和增强
  • 解耦横切关注点(如日志、权限、事务等)

二、如何使用 JDK 动态代理?

步骤 1:定义业务接口

public interface UserService {
    void addUser(String name);
    String getUser(String id);
}

步骤 2:实现业务逻辑

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

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

步骤 3:实现 InvocationHandler

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

public 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("【前置】调用方法: " + method.getName() + ", 参数: " + Arrays.toString(args));

        long start = System.currentTimeMillis();
        try {
            // 执行真实方法
            Object result = method.invoke(target, args);
            System.out.println("【返回】结果: " + result);
            return result;
        } catch (Exception e) {
            System.out.println("【异常】方法执行出错: " + e.getMessage());
            throw e;
        } finally {
            // 后置处理:记录耗时
            long cost = System.currentTimeMillis() - start;
            System.out.println("【耗时】方法 " + method.getName() + " 执行耗时: " + cost + "ms");
        }
    }
}

步骤 4:创建并使用代理对象

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

public class ProxyMain {

    public static void main(String[] args) {
        // 1. 创建目标对象
        UserService userService = new UserServiceImpl();

        // 2. 创建代理处理器
        InvocationHandler handler = new LoggingHandler(userService);

        // 3. 动态生成代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),    // 类加载器
                userService.getClass().getInterfaces(),     // 实现的接口
                handler                                     // 调用处理器
        );

        // 4. 通过代理调用方法
        proxy.addUser("张三");
        System.out.println("---");
        String user = proxy.getUser("1001");
        System.out.println(user);
    }
}

输出结果:

【前置】调用方法: addUser, 参数: [张三]
添加用户: 张三
【返回】结果: null
【耗时】方法 addUser 执行耗时: 0ms
---
【前置】调用方法: getUser, 参数: [1001]
【返回】结果: 用户-1001
【耗时】方法 getUser 执行耗时: 0ms
用户-1001

关键点:Proxy.newProxyInstance() 返回的是一个实现了指定接口的代理对象,所有方法调用都会经过 invoke() 方法。


三、为什么必须基于接口?

JDK 动态代理只能代理实现了接口的类,原因如下:

  • Java 的动态代理机制通过 java.lang.reflect.Proxy 类在运行时生成一个新类
  • 这个新类继承自 Proxy,而 Java 不支持多继承
  • 因此它只能通过实现接口的方式来“伪装”成目标类型
  • 如果目标类没有实现接口,则无法使用 JDK 动态代理(此时可用 CGLIB)

📌 限制:不能代理 final 方法、static 方法,也不能代理没有接口的类。


四、五大典型应用场景详解

场景 1️⃣:统一日志记录(AOP 最常见用途)

问题

每个方法都要写日志,代码重复、难以维护。

解决方案

使用动态代理统一处理日志逻辑。

// 日志代理处理器(复用前面的 LoggingHandler)
// 一行代码即可为所有方法添加日志!
UserService proxy = (UserService) Proxy.newProxyInstance(..., new LoggingHandler(realService));
优势
  • 零侵入:业务代码完全不知道日志的存在
  • 统一管理:修改日志格式只需改一个类
  • 性能监控:轻松统计方法执行时间

场景 2️⃣:权限控制(安全防护)

问题

敏感操作(如删除用户)需要权限校验,但每个方法都写检查逻辑太繁琐。

解决方案
public class SecurityHandler implements InvocationHandler {
    private final Object target;
    
    public SecurityHandler(Object target) { this.target = target; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 对特定方法进行权限校验
        if (method.getName().startsWith("delete") || method.getName().startsWith("ban")) {
            if (!SecurityContext.isAdmin()) {
                throw new SecurityException("权限不足");
            }
        }
        return method.invoke(target, args);
    }
}
优势
  • 集中管控:所有权限逻辑在一个地方
  • 避免遗漏:不会忘记给某个方法加权限检查
  • 易于扩展:新增权限规则只需修改处理器

场景 3️⃣:缓存优化(提升性能)

问题

频繁查询相同数据,造成数据库压力大。

解决方案
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CacheHandler implements InvocationHandler {
    private final Object target;
    private final Map<String, Object> cache = new ConcurrentHashMap<>();

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("getUser".equals(method.getName())) {
            String key = (String) args[0];
            // 先查缓存
            if (cache.containsKey(key)) {
                return cache.get(key);
            }
            // 缓存未命中,查真实服务
            Object result = method.invoke(target, args);
            cache.put(key, result); // 写入缓存
            return result;
        }
        return method.invoke(target, args);
    }
}
优势
  • 透明缓存:调用方无需关心缓存逻辑
  • 性能提升:减少数据库访问次数
  • 线程安全:使用 ConcurrentHashMap 支持并发

场景 4️⃣:RPC 远程调用(分布式系统核心)

问题

客户端调用远程服务需要处理网络通信、序列化等复杂逻辑。

解决方案
public class RpcHandler implements InvocationHandler {
    private final String serviceUrl;
    
    public RpcHandler(String url) { this.serviceUrl = url; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 封装请求:方法名 + 参数
        RpcRequest request = new RpcRequest(method.getName(), args);
        
        // 2. 发送 HTTP/gRPC 请求到服务端
        byte[] responseBytes = HttpClient.post(serviceUrl, serialize(request));
        
        // 3. 反序列化响应并返回
        return deserialize(responseBytes);
    }
}
使用效果
UserService remoteService = (UserService) Proxy.newProxyInstance(
    ..., new RpcHandler("http://user-service/api")
);
// 调用就像本地方法一样!
remoteService.addUser("李四"); // 实际是远程调用
优势
  • 透明远程调用:屏蔽网络细节
  • 接口即契约:客户端只依赖接口
  • 易于集成:Spring Cloud、Dubbo 都基于此思想

场景 5️⃣:单元测试 Mock(提高测试效率)

问题

测试依赖外部服务(如数据库),导致测试慢、不稳定。

解决方案
public class MockHandler implements InvocationHandler {
    private final Map<String, Object> mockReturns = new HashMap<>();
    
    public void whenCall(String methodName, Object returnValue) {
        mockReturns.put(methodName, returnValue);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (mockReturns.containsKey(method.getName())) {
            return mockReturns.get(method.getName());
        }
        throw new UnsupportedOperationException("未模拟的方法: " + method.getName());
    }
}

// 测试代码
@Test
public void testUserService() {
    MockHandler mockHandler = new MockHandler();
    mockHandler.whenCall("getUser", "模拟用户-999");
    
    UserService mockService = (UserService) Proxy.newProxyInstance(
        ..., mockHandler
    );
    
    assertEquals("模拟用户-999", mockService.getUser("any"));
}
优势
  • 快速测试:无需启动数据库
  • 可控性强:可模拟各种返回值(包括异常)
  • 隔离依赖:测试不依赖外部环境

五、动态代理 vs 静态代理 vs CGLIB

特性JDK 动态代理静态代理CGLIB
是否需要接口✅ 必须❌ 不需要❌ 不需要
生成时机运行时编译时运行时
性能较高最高略低(首次)
使用复杂度中等简单中等
适用场景接口丰富项目简单场景无接口类

💡 建议:优先使用 JDK 动态代理(轻量、标准),若目标类无接口再考虑 CGLIB。


六、最佳实践与注意事项

✅ 推荐做法

  1. 代理处理器职责单一:一个 Handler 只做一件事(如只做日志或只做权限)

  2. 组合使用多个代理:可通过嵌套代理实现多重增强

    UserService service = new UserServiceImpl();
    UserService logged = createLoggingProxy(service);
    UserService secured = createSecurityProxy(logged); // 权限+日志
    
  3. 异常处理要谨慎:不要吞掉异常,确保原异常类型不丢失

⚠️ 注意事项

  • 不能代理类本身,只能代理接口
  • invoke() 方法不要递归调用代理对象,否则 StackOverflow
  • 代理对象不能序列化(除非特别处理)
  • 性能开销:反射调用比直接调用慢,但现代 JVM 已大幅优化

七、总结:为什么你需要动态代理?

动态代理的本质是“行为的抽象” --它让你把“怎么做”(如记录日志)和“做什么”(如添加用户)分开。

它解决了什么问题?

  • 代码重复:横切逻辑只需写一次
  • 紧耦合:业务代码不再混杂非核心逻辑
  • 难以维护:修改通用逻辑只需改一处
  • 扩展困难:新增功能无需改动原有代码

一句话记住它:

“我不改你的代码,但我能让你的代码变得更强大。”

无论是 Spring 的 @Transactional、MyBatis 的 Mapper 代理,动态代理都在背后默默工作。掌握它,就掌握了 Java 世界中“无侵入式增强”的魔法钥匙 🔑!


🌟 小提示:在实际项目中,通常不会直接使用 Proxy.newProxyInstance(),而是借助 Spring AOP 等框架。但理解底层原理,才能真正驾驭这些高级工具!