首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java动态代理

Java动态代理

原创
作者头像
艾伦耶格尔
发布2025-08-12 16:18:30
发布2025-08-12 16:18:30
3920
举报
文章被收录于专栏:Java高级Java高级

动态代理就像是一位神奇的魔术师,它能悄无声息地在你的代码运行时,为你的对象添加各种功能,而这一切,你不需要修改任何已有的业务代码!它广泛应用于日志记录、权限控制、事务管理、远程调用(RPC)等场景,是现代Java框架(如Spring AOP、MyBatis)的核心技术之一。

本文将带你深入理解动态代理的本质,掌握JDK动态代理的使用方法,并通过实战案例体会其“无侵入式增强”的魅力。


一、什么是代理?生活中的“中间人”模式

想象一下,你想出国旅游,但你不会外语,也不熟悉签证流程。这时,你可以找一家旅行社——你只需告诉他们你的需求,他们就会帮你安排行程、预订酒店、办理签证。

在这个过程中:

  • 是真实的需求方(相当于 目标对象
  • 旅行社 是中间人(相当于 代理对象
  • 你通过旅行社完成旅行,而不用亲自处理所有细节

在软件开发中,代理模式(Proxy Pattern) 正是这种“中间人”思想的体现。它为某个对象提供一个代理,以控制对原对象的访问,并在访问前后添加额外逻辑,例如:

  • 记录方法执行时间(性能监控)
  • 拦截非法请求(权限校验)
  • 缓存方法结果(提升性能)
  • 自动开启/提交事务(数据库操作)

二、静态代理 vs 动态代理

2.1 静态代理:手动编写代理类

静态代理需要我们提前编写一个代理类,它实现与目标对象相同的接口,并在方法中调用目标对象的方法,同时添加额外逻辑。

代码语言:java
复制
// 代理类
public class UserServiceProxy implements UserService {
    private UserService target;

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

    @Override
    public void createUser(String username, String password) {
        System.out.println("开始执行方法:createUser");
        long start = System.currentTimeMillis();
        
        target.createUser(username, password); // 调用真实对象
        
        long end = System.currentTimeMillis();
        System.out.println("方法执行结束,耗时:" + (end - start) + "ms");
    }

    // deleteUser 同理...
}

❌ 缺点:每个接口都需要写一个代理类,代码重复,维护成本高。

2.2 动态代理:运行时自动生成代理

动态代理 的魔力在于:在程序运行时,由JVM动态生成代理类和代理对象,无需手动编写。它真正实现了“一次编写,处处生效”。

Java 实现动态代理主要有三种方式:

方式

原理

依赖

JDK 动态代理

基于接口,使用 java.lang.reflect.Proxy

JDK 内置

CGLIB 动态代理

基于继承,通过字节码生成子类

第三方库

ASM

直接操作字节码,最底层最灵活

第三方库

✅ 本文重点讲解使用最广泛的 JDK 动态代理


三、JDK 动态代理三剑客

JDK 动态代理的核心是以下三个组件:

1. java.lang.reflect.InvocationHandler 接口

定义代理逻辑的处理者。你需要实现它的 invoke() 方法,在其中编写“方法调用前/后”的增强逻辑。

代码语言:java
复制
public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

2. java.lang.reflect.Method

表示一个具体的方法对象。通过它可以在运行时调用目标方法(反射机制)。

3. java.lang.reflect.Proxy

JDK 提供的工具类,用于动态生成代理类并创建代理实例

核心方法:

代码语言:java
复制
public static Object newProxyInstance(
    ClassLoader loader,      // 类加载器
    Class<?>[] interfaces,   // 目标对象实现的接口数组
    InvocationHandler h      // 代理逻辑处理器
)

四、实战:打造万能日志记录器

假设我们有一个用户服务,现在想在不修改原有代码的前提下,为所有方法添加执行时间日志功能。

4.1 定义接口与实现类

代码语言:java
复制
// 用户服务接口
public interface UserService {
    void createUser(String username, String password);
    void deleteUser(String username);
}

// 业务实现类(无需任何改动!)
public class UserServiceImpl implements UserService {
    @Override
    public void createUser(String username, String password) {
        System.out.println("✅ 创建用户:" + username);
        simulateDelay(); // 模拟业务耗时
    }

    @Override
    public void deleteUser(String username) {
        System.out.println("❌ 删除用户:" + username);
        simulateDelay();
    }

    private void simulateDelay() {
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
    }
}

4.2 编写 InvocationHandler 实现类

代码语言:java
复制
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 日志代理处理器
public class LogInvocationHandler implements InvocationHandler {
    private final Object target; // 目标对象

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();

        // 【前置增强】方法执行前
        System.out.println("🎬 开始执行方法:" + methodName);
        long startTime = System.nanoTime();

        // 【核心逻辑】通过反射调用目标方法
        Object result = method.invoke(target, args);

        // 【后置增强】方法执行后
        long endTime = System.nanoTime();
        long duration = (endTime - startTime) / 1_000_000; // 毫秒
        System.out.println("🏁 方法执行结束:" + methodName + ",耗时:" + duration + "ms");

        return result; // 返回原方法结果
    }
}

4.3 创建代理对象并测试

代码语言:java
复制
import java.lang.reflect.Proxy;

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

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

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

        // 4. 调用代理对象的方法(自动触发日志)
        proxy.createUser("zhangsan", "123456");
        proxy.deleteUser("lisi");
    }
}

✅ 运行结果

代码语言:java
复制
🎬 开始执行方法:createUser
✅ 创建用户:zhangsan
🏁 方法执行结束:createUser,耗时:104ms
🎬 开始执行方法:deleteUser
❌ 删除用户:lisi
🏁 方法执行结束:deleteUser,耗时:103ms

✅ 成功!无需修改 UserServiceImpl,日志功能自动生效。


五、深入理解 invoke() 方法

invoke() 是动态代理的“心脏”,它在每次调用代理对象的方法时被触发。

代码语言:java
复制
public Object invoke(Object proxy, Method method, Object[] args)

参数

说明

proxy

当前生成的代理对象本身

method

被调用的方法对象(反射)

args

方法的参数数组

💡 你可以通过 method.getName() 判断当前调用的是哪个方法,从而实现差异化处理,比如只对 save* 方法加事务,对 query* 方法加缓存。


六、JDK 动态代理的底层原理(简要)

当你调用 Proxy.newProxyInstance() 时,JVM 会:

  1. 使用类加载器动态生成一个代理类(如 UserServiceProxy0
  2. 该类实现你传入的所有接口
  3. 所有方法都会调用你提供的 InvocationHandler.invoke()
  4. 将生成的类加载到 JVM 中,并创建其实例返回

🔍 你可以通过 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 参数将生成的代理类 .class 文件保存到磁盘查看。


七、动态代理的典型应用场景

场景

说明

AOP(面向切面编程)

Spring AOP 的核心实现机制

事务管理

在方法前后自动开启/提交事务

RPC 框架

如 Dubbo、gRPC,客户端通过代理调用远程服务

缓存增强

查询前查缓存,查询后更新缓存

权限校验

调用前检查用户是否有权限

Mock 测试

Mockito 等测试框架使用代理生成 Mock 对象


八、动态代理的优缺点

优点

说明

✅ 无侵入性

不修改原有业务代码,符合开闭原则

✅ 高度解耦

增强逻辑与业务逻辑分离

✅ 灵活复用

同一套代理逻辑可用于多个目标对象

✅ 易于维护

增强逻辑集中管理

缺点

说明

⚠️ 性能开销

反射调用比直接调用慢

⚠️ 只能代理接口

JDK 动态代理要求目标类必须实现接口

⚠️ 调试困难

代理对象是运行时生成的,堆栈信息复杂

⚠️ 不支持 final 类/方法

无法继承或重写

💡 补充说明:如果目标类没有实现接口,可以使用 CGLIB(基于继承生成子类)或 AspectJ(编译期织入)来实现。


九、最佳实践建议

  1. 优先使用接口编程:为JDK动态代理创造条件。
  2. 避免高频调用场景:性能敏感的方法慎用反射代理。
  3. 合理封装 InvocationHandler:可设计通用的 AbstractAspectHandler 抽象类。
  4. 结合Spring使用:实际开发中,推荐使用Spring AOP,它封装了动态代理的复杂性。
  5. 注意异常处理invoke() 中的异常要妥善处理,避免吞掉原异常。

结语

动态代理是Java语言“元编程”能力的体现,它让程序具备了在运行时自我增强的能力。掌握JDK动态代理,不仅能帮助你理解Spring AOP等框架的底层原理,也能让你在设计系统时,写出更加灵活、可扩展、低耦合的代码。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是代理?生活中的“中间人”模式
  • 二、静态代理 vs 动态代理
    • 2.1 静态代理:手动编写代理类
    • 2.2 动态代理:运行时自动生成代理
  • 三、JDK 动态代理三剑客
    • 1. java.lang.reflect.InvocationHandler 接口
    • 2. java.lang.reflect.Method 类
    • 3. java.lang.reflect.Proxy 类
  • 四、实战:打造万能日志记录器
    • 4.1 定义接口与实现类
    • 4.2 编写 InvocationHandler 实现类
    • 4.3 创建代理对象并测试
    • ✅ 运行结果
  • 五、深入理解 invoke() 方法
  • 六、JDK 动态代理的底层原理(简要)
  • 七、动态代理的典型应用场景
  • 八、动态代理的优缺点
  • 九、最佳实践建议
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档