Java动态代理完全指南:原理与实战

这是一份非常详细、实用、通俗易懂且权威全面的 Java 动态代理指南。


Java 动态代理完全指南:原理、实战与经典案例

目录

  1. 概述
    • 1.1 什么是代理模式?
    • 1.2 静态代理 vs 动态代理
    • 1.3 Java 动态代理的核心价值与应用场景
  2. 核心机制:Proxy 类与 InvocationHandler 接口
    • 2.1 java.lang.reflect.Proxy 类详解
    • 2.2 java.lang.reflect.InvocationHandler 接口详解
    • 2.3 动态代理的创建过程剖析
  3. 实战演练:基础用法
    • 3.1 创建一个简单的动态代理:日志记录示例
    • 3.2 代码详解与在 IDE 中运行
  4. 进阶理解
    • 4.1 动态代理的原理:运行时生成代理类
    • 4.2 动态代理的局限性
    • 4.3 性能考量
  5. 最佳实践
    • 5.1 接口设计建议
    • 5.2 InvocationHandler 实现技巧
    • 5.3 异常处理
  6. 经典实战案例
    • 6.1 案例一:数据库事务控制 (模拟)
    • 6.2 案例二:RPC 框架中的远程方法调用 (模拟)
    • 6.3 案例三:Spring AOP 风格的日志与性能监控
  7. 总结

1. 概述

1.1 什么是代理模式?

想象一下,你想找明星拍广告,但你不会直接去找明星本人,而是通过他的经纪人 。经纪人就相当于明星的代理。代理模式(Proxy Pattern)就是这种思想在编程中的应用。

  • 目标对象 (Real Subject):真正执行业务逻辑的对象(如明星)。
  • 代理对象 (Proxy):代替目标对象处理请求的对象(如经纪人)。客户端通常直接与代理对象交互。
  • 作用 :代理对象可以在调用目标对象的方法之前之后 ,添加额外的操作(如安全检查、日志记录、事务管理等),而无需修改目标对象本身的代码 。这遵循了开闭原则(对扩展开放,对修改关闭)。

1.2 静态代理 vs 动态代理

  • 静态代理
    • 手动编写代理类:程序员需要为每个需要代理的接口或类,显式地编写一个实现了相同接口或继承了相同父类的代理类。
    • 缺点:如果有很多类需要代理,或者接口方法很多,编写和维护代理类会非常繁琐。
  • 动态代理
    • 运行时 生成代理类:程序在运行时,根据指定的接口(或一组接口),动态地创建代理类的字节码并加载到 JVM 中。
    • 核心机制 :通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。
    • 优点 :一个通用的 InvocationHandler 实现可以服务于多个不同的接口,大大减少代码量,提高灵活性。

1.3 Java 动态代理的核心价值与应用场景

  • 核心价值 :提供一种在运行时动态创建代理对象的能力,无需编写具体的代理类代码,增强了程序的灵活性和可扩展性
  • 应用场景
    • AOP(面向切面编程):如日志记录、性能监控、事务管理、安全检查等。这是动态代理最经典的应用。
    • 远程方法调用(RPC):客户端调用代理对象的方法时,代理对象负责将调用信息(方法名、参数等)序列化并通过网络发送到服务端,并将服务端返回的结果反序列化后返回给客户端。
    • 延迟初始化:代理对象在第一次被调用时才真正创建或加载目标对象。
    • 访问控制:在调用目标方法前进行权限校验。
    • 缓存:代理对象可以缓存目标方法的调用结果。

2. 核心机制:Proxy 类与 InvocationHandler 接口

Java 动态代理的核心依赖于 JDK 提供的两个关键组件:

2.1 java.lang.reflect.Proxy

Proxy 类是用于创建动态代理类和实例的工厂类。它提供了一组静态方法来生成代理对象。

  • 核心方法

    java 复制代码
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    • loader:定义代理类的类加载器 。通常使用目标接口的类加载器(targetInterface.getClassLoader())。
    • interfaces:代理类需要实现的接口列表 。这是一个 Class 对象数组。非常重要:动态代理只能基于接口!不能基于类!
    • hInvocationHandler 接口的实现对象。所有对代理对象的方法调用都会转发 给这个 InvocationHandlerinvoke 方法处理。
    • 返回值:一个实现了指定接口列表的代理对象实例。

2.2 java.lang.reflect.InvocationHandler 接口

InvocationHandler 是一个调用处理器接口。它只有一个方法需要实现:

java 复制代码
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
  • proxy:代理对象本身(即 Proxy.newProxyInstance 返回的对象)。注意:invoke 方法内部,如果调用 proxy 对象的方法(如 proxy.toString()),且这个方法不是 Object 类的方法,会再次触发 invoke 调用,可能导致无限递归。调用 proxyObject 方法(如 hashCode, equals, toString)通常是安全的,因为它们不会被代理。
  • method:客户端正在调用的代理对象上的方法 对应的 Method 对象。通过它,你可以知道客户端调用了哪个方法。
  • args:客户端调用方法时传递的参数值数组 。如果方法没有参数,则为 null
  • 返回值 :代理方法应该返回的值。这通常是调用目标对象对应方法的结果(method.invoke(target, args)),但也可能是你自己构造的结果。
  • 职责InvocationHandlerinvoke 方法负责处理 所有对代理对象方法的调用。在这里,你可以:
    • 在调用目标方法前执行操作(如日志、权限检查)。
    • 调用目标对象的方法(这是关键步骤)。
    • 在调用目标方法后执行操作(如日志、结果处理)。
    • 处理异常。

2.3 动态代理的创建过程剖析

  1. 定义接口:首先需要一个或多个接口,定义代理对象应该具备的行为。
  2. 实现目标对象:编写实现了这些接口的具体类(目标对象)。
  3. 实现 InvocationHandler :编写一个类实现 InvocationHandler 接口。在这个类的 invoke 方法中,定义代理逻辑(如调用目标对象的方法、添加额外操作)。
  4. 创建代理对象 :调用 Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)
    • JVM 在运行时,根据指定的接口列表 (interfaces),动态生成一个新的类(代理类)的字节码。这个类实现了所有指定的接口。
    • 这个生成的代理类内部,会持有你提供的 InvocationHandler 对象 (h)。
    • 代理类中每个接口方法的实现,本质上都是直接调用 h.invoke(this, method, args)
  5. 使用代理对象 :客户端代码获得代理对象后,将其当作普通接口实现类来使用。当调用其方法时,请求被转发到 InvocationHandlerinvoke 方法,由它决定如何处理(通常最终会调用真实的目标对象方法)。

3. 实战演练:基础用法

3.1 创建一个简单的动态代理:日志记录示例

让我们用一个最简单的例子演示动态代理:在方法调用前后打印日志。

步骤 1:定义接口 (UserService)

java 复制代码
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
    String getUser(String username);
}

步骤 2:实现目标对象 (UserServiceImpl)

java 复制代码
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("添加用户: " + username);
        // 模拟实际添加操作
    }

    @Override
    public void deleteUser(String username) {
        System.out.println("删除用户: " + username);
        // 模拟实际删除操作
    }

    @Override
    public String getUser(String username) {
        System.out.println("获取用户: " + username);
        // 模拟实际查询操作
        return "用户信息: " + username;
    }
}

步骤 3:实现 InvocationHandler (LoggingHandler)

java 复制代码
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() + ", 参数: " + (args != null ? java.util.Arrays.toString(args) : "无"));

        // 调用目标对象上的原始方法
        Object result = method.invoke(target, args);

        // 方法调用后打印日志 (注意:如果方法返回void,result为null)
        System.out.println("[日志] 方法执行完成: " + method.getName() + ", 返回结果: " + (result != null ? result.toString() : "void"));

        // 将调用结果返回给调用者
        return result;
    }
}

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

java 复制代码
import java.lang.reflect.Proxy;

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

        // 2. 创建 InvocationHandler,传入真实对象
        InvocationHandler handler = new LoggingHandler(realService);

        // 3. 使用 Proxy 创建代理对象
        // 注意:这里转换成了 UserService 接口类型
        UserService proxyService = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(), // 类加载器
                new Class<?>[]{UserService.class},  // 代理类要实现的接口列表
                handler);                           // 调用处理器

        // 4. 客户端使用代理对象
        proxyService.addUser("张三");
        proxyService.deleteUser("李四");
        String userInfo = proxyService.getUser("王五");
        System.out.println("客户端收到的用户信息: " + userInfo);

        // 5. (可选) 查看代理类的名字 (通常包含 $Proxy 字样)
        System.out.println("代理对象的类名: " + proxyService.getClass().getName());
    }
}

3.2 代码详解与在 IDE 中运行

  • 接口 (UserService): 定义了业务操作。
  • 目标对象 (UserServiceImpl): 实现了接口,包含实际的业务逻辑。
  • 调用处理器 (LoggingHandler) :
    • 持有一个 target 对象引用(这里是 UserServiceImpl 实例)。
    • invoke 方法中:
      1. 打印方法开始执行的日志(包含方法名和参数)。
      2. 使用反射 method.invoke(target, args) 调用目标对象上的原始方法。
      3. 打印方法执行完成的日志(包含方法名和返回值)。
      4. 将原始方法的返回值返回给代理方法的调用者。
  • 主类,创建代理 (DynamicProxyDemo) :
    • 创建真实对象 realService
    • 创建 LoggingHandler,将 realService 传入。
    • 调用 Proxy.newProxyInstance 创建代理对象 proxyService。注意转换类型为接口 UserService
    • 使用 proxyService 调用方法。这些调用会被 LoggingHandler 拦截并添加日志。
  • 在 IDE 中运行
    • 将上述四个类 (UserService, UserServiceImpl, LoggingHandler, DynamicProxyDemo) 复制到你的 Java 项目中。
    • 运行 DynamicProxyDemomain 方法。
    • 观察控制台输出,你会看到每次方法调用前后都添加了日志信息。

预期输出示例:

复制代码
[日志] 开始执行方法: addUser, 参数: [张三]
添加用户: 张三
[日志] 方法执行完成: addUser, 返回结果: void
[日志] 开始执行方法: deleteUser, 参数: [李四]
删除用户: 李四
[日志] 方法执行完成: deleteUser, 返回结果: void
[日志] 开始执行方法: getUser, 参数: [王五]
获取用户: 王五
[日志] 方法执行完成: getUser, 返回结果: 用户信息: 王五
客户端收到的用户信息: 用户信息: 王五
代理对象的类名: com.sun.proxy.$Proxy0

4. 进阶理解

4.1 动态代理的原理:运行时生成代理类

Proxy.newProxyInstance 方法在运行时执行了以下关键步骤:

  1. 类加载器检查 :验证给定的 ClassLoaderinterfaces 是否能一起工作。

  2. 代理类查找或生成

    • 首先尝试从缓存(通常是 loader 的缓存)中查找是否已生成过实现了这些 interfaces 的代理类。
    • 如果没有,则使用 ProxyGenerator(或类似机制)动态生成代理类的字节码 (byte[])。
  3. 字节码加载 :使用提供的 ClassLoader 将生成的字节码加载到 JVM 中,定义一个新的 Class 对象(代理类)。

  4. 实例化代理对象

    • 通过反射获取代理类的构造函数(该构造函数需要 InvocationHandler 参数)。
    • 使用 InvocationHandler 实例 (h) 调用构造函数,创建代理对象实例。
  5. 代理类结构 :生成的代理类大致如下(伪代码):

    java 复制代码
    public final class $Proxy0 extends Proxy implements UserService { // 实现指定的接口
        private static Method m1, m2, m3; // 对应接口方法的 Method 对象
    
        static {
            try {
                m1 = Class.forName("UserService").getMethod("addUser", String.class);
                m2 = ... // deleteUser
                m3 = ... // getUser
            } catch (Exception e) { ... }
        }
    
        public $Proxy0(InvocationHandler h) {
            super(h); // 调用 Proxy 的构造函数
        }
    
        @Override
        public void addUser(String username) {
            try {
                super.h.invoke(this, m1, new Object[]{username}); // 调用处理器
            } catch (Throwable e) { ... }
        }
        // 其他方法类似...
    }
    • 代理类继承自 java.lang.reflect.Proxy
    • 实现了你指定的所有接口 (UserService)。
    • 每个接口方法的实现,都是直接调用 InvocationHandlerinvoke 方法 (super.h.invoke(...)),并将 proxy(自身)、Method 对象、参数数组传递过去。

4.2 动态代理的局限性

  • 只能基于接口 :这是 JDK 动态代理最大的限制。代理类必须实现一个或多个接口。它不能代理一个没有实现任何接口的普通类 。如果需要代理类,可以考虑使用 CGLIBByte BuddyJavassist 等第三方字节码操作库。
  • 接口方法必须是 public :JDK 动态代理只能代理接口中的 public 方法。protectedprivate 或包级私有的方法无法被代理。
  • 性能开销 :反射调用 (method.invoke()) 比直接调用方法慢。虽然现代 JVM 对反射进行了优化,但在极端性能敏感的场景仍需注意。动态生成类本身也有开销,但通常在应用启动时一次性完成,影响较小。
  • 代理对象类型 :代理对象是 Proxy 的子类 ($Proxy0 等),不是目标类的子类。因此不能直接将其强制转换为目标类类型(只能转换为接口类型)。

4.3 性能考量

  • 在绝大多数应用场景中,JDK 动态代理的性能开销是可以接受的。
  • 如果确实遇到性能瓶颈:
    • 考虑是否过度使用了代理。
    • 评估使用第三方库(如 CGLIB)是否更适合(CGLIB 通过生成目标类的子类来代理,避免了部分反射调用,但创建代理对象可能更慢)。
    • 对于非常高频的调用,可以考虑在 InvocationHandler 内部对某些方法做特殊处理(如直接调用目标对象的方法,避免反射),但这会牺牲通用性。

5. 最佳实践

5.1 接口设计建议

  • 职责单一 :接口应专注于一组紧密相关的功能。这样 InvocationHandler 的实现逻辑会更清晰。
  • 避免暴露过多方法 :代理所有接口方法。如果接口中有大量方法,而你的 InvocationHandler 只关心其中几个,会导致不必要的调用转发。可以考虑重构接口。
  • 清晰的命名 :方法名应准确反映其功能,便于在 InvocationHandler 中识别和处理。

5.2 InvocationHandler 实现技巧

  • 保持轻量invoke 方法中的额外逻辑(如日志、校验)应尽量高效。避免在此处执行耗时的 I/O 或复杂计算。

  • 区分方法处理 :根据 method.getName()method 对象本身,对不同的方法执行不同的代理逻辑。例如,只对 update* 方法添加事务控制。

    java 复制代码
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (methodName.startsWith("update") || methodName.startsWith("delete")) {
            // 事务相关逻辑
            return doInTransaction(() -> method.invoke(target, args));
        } else {
            // 普通调用
            return method.invoke(target, args);
        }
    }
  • 处理 Object 方法 :对于 toString, hashCode, equalsObject 类的方法,通常需要特殊处理以避免无限递归或错误行为。常见的做法是:

    • toString():可以返回代理对象和真实对象的组合信息。
    • hashCode() :可以返回真实对象的 hashCode() 或代理对象的 System.identityHashCode(proxy)
    • equals():比较代理对象时,通常应比较它们包装的真实对象是否相同。
    java 复制代码
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?> declaringClass = method.getDeclaringClass();
    
        // 处理 Object 类的方法
        if (declaringClass == Object.class) {
            if ("equals".equals(methodName)) {
                Object arg = args[0];
                // 如果参数也是代理,比较其目标对象;否则直接比较
                return (proxy == arg) || (arg instanceof Proxy && this.target.equals(Proxy.getInvocationHandler(arg).getTarget()));
            } else if ("hashCode".equals(methodName)) {
                return System.identityHashCode(proxy); // 或 target.hashCode();
            } else if ("toString".equals(methodName)) {
                return "Proxy[" + target.toString() + "]";
            }
        }
        // ... 其他方法处理
    }

    (注意:getTarget() 需要在自定义 InvocationHandler 中提供访问 target 的方法)

  • 封装目标对象访问 :在自定义 InvocationHandler 中提供一个方法来获取其持有的目标对象(如 getTarget()),方便调试或特殊场景使用。

5.3 异常处理

  • 传递原始异常method.invoke(target, args) 会抛出 InvocationTargetException。这个异常的 getCause() 方法包含了目标方法本身抛出的原始异常(如 SQLException, IOException)。通常你应该在 InvocationHandlerinvoke 方法中捕获 InvocationTargetException,并将 getCause() 抛给上层调用者,这样客户端看到的是目标方法抛出的异常类型。

    java 复制代码
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // ... 前置操作
            Object result = method.invoke(target, args);
            // ... 后置操作
            return result;
        } catch (InvocationTargetException e) {
            // 抛出目标方法产生的原始异常
            throw e.getCause();
        }
        // 处理其他异常...
    }
  • 包装异常:如果需要,你也可以将原始异常包装成自定义的业务异常再抛出。

  • 记录异常 :在 InvocationHandler 中统一记录方法调用异常是一个常见做法(AOP 异常日志)。


6. 经典实战案例

下面提供三个更贴近实际应用场景的详细案例。每个案例都包含完整可运行的代码。

6.1 案例一:数据库事务控制 (模拟)

场景 :在方法执行前后管理数据库事务(开启、提交/回滚)。这里简化了数据库操作,使用 System.out.println 模拟。

接口 (AccountService)

java 复制代码
public interface AccountService {
    void transfer(String fromAccount, String toAccount, double amount);
}

目标对象 (AccountServiceImpl)

java 复制代码
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer(String fromAccount, String toAccount, double amount) {
        // 模拟实际的转账数据库操作
        System.out.println("执行转账: " + fromAccount + " -> " + toAccount + ", 金额: " + amount);
        // 这里可能抛出 RuntimeException 模拟失败
        if (amount > 1000) {
            throw new RuntimeException("转账金额过大!");
        }
    }
}

事务管理器 (TransactionManager) - 模拟

java 复制代码
public class TransactionManager {
    public void begin() {
        System.out.println("事务管理器: 开启事务");
    }

    public void commit() {
        System.out.println("事务管理器: 提交事务");
    }

    public void rollback() {
        System.out.println("事务管理器: 回滚事务");
    }
}

事务控制的 InvocationHandler (TransactionHandler)

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

public class TransactionHandler implements InvocationHandler {

    private final Object target; // 真实对象
    private final TransactionManager transactionManager; // 事务管理器

    public TransactionHandler(Object target, TransactionManager transactionManager) {
        this.target = target;
        this.transactionManager = transactionManager;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        try {
            // 开启事务
            transactionManager.begin();
            // 执行目标方法
            result = method.invoke(target, args);
            // 提交事务
            transactionManager.commit();
        } catch (Exception e) {
            // 出现异常,回滚事务
            transactionManager.rollback();
            // 抛出原始异常
            throw e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e;
        }
        return result;
    }
}

客户端使用 (TransactionDemo)

java 复制代码
import java.lang.reflect.Proxy;

public class TransactionDemo {
    public static void main(String[] args) {
        // 创建真实对象和事务管理器
        AccountService realService = new AccountServiceImpl();
        TransactionManager txManager = new TransactionManager();

        // 创建 TransactionHandler
        InvocationHandler handler = new TransactionHandler(realService, txManager);

        // 创建代理对象
        AccountService proxyService = (AccountService) Proxy.newProxyInstance(
                AccountService.class.getClassLoader(),
                new Class<?>[]{AccountService.class},
                handler);

        // 使用代理对象进行转账 (正常)
        try {
            proxyService.transfer("A001", "B002", 500.0);
        } catch (Exception e) {
            System.out.println("转账异常: " + e.getMessage());
        }

        // 使用代理对象进行转账 (会失败并回滚)
        try {
            proxyService.transfer("A001", "B002", 2000.0);
        } catch (Exception e) {
            System.out.println("转账异常: " + e.getMessage());
        }
    }
}

预期输出:

复制代码
事务管理器: 开启事务
执行转账: A001 -> B002, 金额: 500.0
事务管理器: 提交事务
事务管理器: 开启事务
执行转账: A001 -> B002, 金额: 2000.0
事务管理器: 回滚事务
转账异常: 转账金额过大!

6.2 案例二:RPC 框架中的远程方法调用 (模拟)

场景:客户端调用代理对象的方法,代理对象负责将方法名、参数等信息序列化并通过网络发送给远程服务端,接收结果并返回给客户端。这里简化了网络通信和序列化。

服务接口 (UserService - 同案例1)

java 复制代码
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
    String getUser(String username);
}

远程调用处理器 (RpcInvocationHandler)

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

public class RpcInvocationHandler implements InvocationHandler {

    // 模拟远程服务地址
    private final String serverAddress;

    public RpcInvocationHandler(String serverAddress) {
        this.serverAddress = serverAddress;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 构建 RPC 请求信息 (简化)
        String request = buildRpcRequest(method, args);
        System.out.println("[客户端] 发送 RPC 请求到 " + serverAddress + ": " + request);

        // 2. 模拟网络发送请求并接收响应 (这里简化,直接返回一个结果字符串)
        // 在实际 RPC 框架中,这里会使用网络库发送序列化后的数据包
        String response = "[服务端响应] 方法: " + method.getName() + ", 结果: OK";
        System.out.println("[客户端] 收到 RPC 响应: " + response);

        // 3. 解析响应并返回结果 (简化)
        // 对于非 void 方法,需要根据响应构造返回值
        if (method.getReturnType() == void.class) {
            return null;
        } else {
            return response;
        }
    }

    private String buildRpcRequest(Method method, Object[] args) {
        return "RPC调用: 接口=" + method.getDeclaringClass().getSimpleName() +
                ", 方法=" + method.getName() +
                ", 参数=" + java.util.Arrays.toString(args);
    }
}

客户端使用 (RpcClientDemo)

java 复制代码
import java.lang.reflect.Proxy;

public class RpcClientDemo {
    public static void main(String[] args) {
        // 创建 RpcInvocationHandler,指定远程服务地址
        InvocationHandler handler = new RpcInvocationHandler("http://rpc-server:8080");

        // 创建代理对象
        UserService proxyService = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(),
                new Class<?>[]{UserService.class},
                handler);

        // 客户端像调用本地方法一样使用代理对象
        proxyService.addUser("远程用户");
        String user = proxyService.getUser("远程用户");
        System.out.println("客户端收到远程用户信息: " + user);
    }
}

预期输出:

复制代码
[客户端] 发送 RPC 请求到 http://rpc-server:8080: RPC调用: 接口=UserService, 方法=addUser, 参数=[远程用户]
[客户端] 收到 RPC 响应: [服务端响应] 方法: addUser, 结果: OK
[客户端] 发送 RPC 请求到 http://rpc-server:8080: RPC调用: 接口=UserService, 方法=getUser, 参数=[远程用户]
[客户端] 收到 RPC 响应: [服务端响应] 方法: getUser, 结果: OK
客户端收到远程用户信息: [服务端响应] 方法: getUser, 结果: OK

6.3 案例三:Spring AOP 风格的日志与性能监控

场景:结合日志记录和性能监控(方法执行耗时)。

接口 (ProductService)

java 复制代码
public interface ProductService {
    Product getProductById(int id);
    void updateProductPrice(int id, double newPrice);
}

实体类 (Product)

java 复制代码
public class Product {
    private int id;
    private String name;
    private double price;

    public Product(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Product{id=" + id + ", name='" + name + '\'' + ", price=" + price + '}';
    }
}

目标对象 (ProductServiceImpl)

java 复制代码
public class ProductServiceImpl implements ProductService {
    @Override
    public Product getProductById(int id) {
        // 模拟数据库查询耗时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new Product(id, "产品" + id, 100.0 * id);
    }

    @Override
    public void updateProductPrice(int id, double newPrice) {
        // 模拟更新操作耗时
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("更新产品 " + id + " 价格为: " + newPrice);
    }
}

AOP 风格的 InvocationHandler (AopLoggingHandler)

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

public class AopLoggingHandler implements InvocationHandler {

    private final Object target;

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

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

        // 记录方法开始
        System.out.println("[AOP-Log] ==> 方法 " + methodName + " 开始执行. 参数: " + java.util.Arrays.toString(args));

        Object result = null;
        try {
            // 执行目标方法
            result = method.invoke(target, args);
            return result;
        } finally {
            long endTime = System.currentTimeMillis();
            long duration = endTime - startTime;
            // 记录方法结束和耗时
            System.out.println("[AOP-Log] <== 方法 " + methodName + " 执行完成. 耗时: " + duration + "ms. " +
                    (method.getReturnType() == void.class ? "" : "结果: " + result));
        }
    }
}

客户端使用 (AopDemo)

java 复制代码
import java.lang.reflect.Proxy;

public class AopDemo {
    public static void main(String[] args) {
        // 创建真实对象
        ProductService realService = new ProductServiceImpl();

        // 创建 AopLoggingHandler
        InvocationHandler handler = new AopLoggingHandler(realService);

        // 创建代理对象
        ProductService proxyService = (ProductService) Proxy.newProxyInstance(
                ProductService.class.getClassLoader(),
                new Class<?>[]{ProductService.class},
                handler);

        // 使用代理对象
        Product product = proxyService.getProductById(1);
        System.out.println("获取到的产品: " + product);

        proxyService.updateProductPrice(2, 250.0);
    }
}

预期输出:

复制代码
[AOP-Log] ==> 方法 getProductById 开始执行. 参数: [1]
[AOP-Log] <== 方法 getProductById 执行完成. 耗时: 100ms. 结果: Product{id=1, name='产品1', price=100.0}
获取到的产品: Product{id=1, name='产品1', price=100.0}
[AOP-Log] ==> 方法 updateProductPrice 开始执行. 参数: [2, 250.0]
更新产品 2 价格为: 250.0
[AOP-Log] <== 方法 updateProductPrice 执行完成. 耗时: 50ms.

7. 总结

Java 动态代理 (java.lang.reflect.ProxyInvocationHandler) 是 Java 反射 API 提供的一个强大工具,用于在运行时动态创建实现了指定接口的代理对象。它的核心价值在于实现无侵入式方法拦截功能增强。通过本指南的学习和代码实践,你应该能够深入理解 Java 动态代理的原理,掌握其核心 API 的使用方法,并能在实际项目中应用它来解决诸如日志记录、事务管理、远程调用等常见问题。

相关推荐
小北方城市网4 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
六义义4 小时前
java基础十二
java·数据结构·算法
毕设源码-钟学长5 小时前
【开题答辩全过程】以 基于SpringBoot的智能书城推荐系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
笨手笨脚の5 小时前
深入理解 Java 虚拟机-03 垃圾收集
java·jvm·垃圾回收·标记清除·标记复制·标记整理
莫问前路漫漫6 小时前
WinMerge v2.16.41 中文绿色版深度解析:文件对比与合并的全能工具
java·开发语言·python·jdk·ai编程
九皇叔叔6 小时前
【03】SpringBoot3 MybatisPlus BaseMapper 源码分析
java·开发语言·mybatis·mybatis plus
挖矿大亨6 小时前
c++中的函数模版
java·c++·算法
a程序小傲7 小时前
得物Java面试被问:RocketMQ的消息轨迹追踪实现
java·linux·spring·面试·职场和发展·rocketmq·java-rocketmq
青春男大7 小时前
Redis和RedisTemplate快速上手
java·数据库·redis·后端·spring·缓存
Ghost Face...7 小时前
i386 CPU页式存储管理深度解析
java·linux·服务器